mod fields;
use std::fmt::Debug;
use std::fmt::Formatter;
use std::iter;
use std::sync::Arc;
use apollo_compiler::Name;
use apollo_compiler::Node;
use apollo_compiler::Schema;
use apollo_compiler::ast::Argument;
use apollo_compiler::ast::Directive;
use apollo_compiler::ast::DirectiveDefinition;
use apollo_compiler::ast::DirectiveList;
use apollo_compiler::ast::DirectiveLocation;
use apollo_compiler::ast::EnumValueDefinition;
use apollo_compiler::ast::FieldDefinition;
use apollo_compiler::ast::NamedType;
use apollo_compiler::ast::Type;
use apollo_compiler::ast::Value;
use apollo_compiler::collections::HashMap;
use apollo_compiler::collections::IndexMap;
use apollo_compiler::collections::IndexSet;
use apollo_compiler::name;
use apollo_compiler::schema::Component;
use apollo_compiler::schema::EnumType;
use apollo_compiler::schema::ExtendedType;
use apollo_compiler::schema::Implementers;
use apollo_compiler::schema::InputObjectType;
use apollo_compiler::schema::InputValueDefinition;
use apollo_compiler::schema::InterfaceType;
use apollo_compiler::schema::ObjectType;
use apollo_compiler::schema::ScalarType;
use apollo_compiler::schema::UnionType;
use apollo_compiler::ty;
use apollo_compiler::validation::Valid;
use indexmap::map::Entry::Occupied;
use indexmap::map::Entry::Vacant;
use indexmap::map::Iter;
use itertools::Itertools;
use crate::ValidFederationSubgraph;
use crate::ValidFederationSubgraphs;
use crate::error::FederationError;
use crate::link::LinksMetadata;
use crate::link::federation_spec_definition::FEDERATION_EXTERNAL_DIRECTIVE_NAME_IN_SPEC;
use crate::link::federation_spec_definition::FEDERATION_FIELDS_ARGUMENT_NAME;
use crate::link::federation_spec_definition::FEDERATION_FROM_ARGUMENT_NAME;
use crate::link::federation_spec_definition::FEDERATION_INTERFACEOBJECT_DIRECTIVE_NAME_IN_SPEC;
use crate::link::federation_spec_definition::FEDERATION_KEY_DIRECTIVE_NAME_IN_SPEC;
use crate::link::federation_spec_definition::FEDERATION_OVERRIDE_DIRECTIVE_NAME_IN_SPEC;
use crate::link::federation_spec_definition::FEDERATION_OVERRIDE_LABEL_ARGUMENT_NAME;
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::inaccessible_spec_definition::INACCESSIBLE_DIRECTIVE_NAME_IN_SPEC;
use crate::link::inaccessible_spec_definition::InaccessibleSpecDefinition;
use crate::link::join_spec_definition::EnumValue;
use crate::link::join_spec_definition::JOIN_OVERRIDE_LABEL_ARGUMENT_NAME;
use crate::link::spec::Identity;
use crate::link::spec::Version;
use crate::link::spec_definition::SpecDefinition;
use crate::schema::ValidFederationSchema;
use crate::subgraph::ValidSubgraph;
type MergeWarning = String;
type MergeError = String;
struct Merger {
errors: Vec<MergeError>,
composition_hints: Vec<MergeWarning>,
needs_inaccessible: bool,
interface_objects: IndexSet<Name>,
}
pub struct MergeSuccess {
pub schema: Valid<Schema>,
pub composition_hints: Vec<MergeWarning>,
}
impl From<FederationError> for MergeFailure {
fn from(err: FederationError) -> Self {
MergeFailure {
schema: None,
errors: vec![err.to_string()],
composition_hints: vec![],
}
}
}
pub struct MergeFailure {
pub schema: Option<Box<Schema>>,
pub errors: Vec<MergeError>,
pub composition_hints: Vec<MergeWarning>,
}
impl Debug for MergeFailure {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
f.debug_struct("MergeFailure")
.field("errors", &self.errors)
.field("composition_hints", &self.composition_hints)
.finish()
}
}
pub fn merge_subgraphs(subgraphs: Vec<&ValidSubgraph>) -> Result<MergeSuccess, MergeFailure> {
let mut merger = Merger::new();
let mut federation_subgraphs = ValidFederationSubgraphs::new();
for subgraph in subgraphs {
federation_subgraphs.add(ValidFederationSubgraph {
name: subgraph.name.clone(),
url: subgraph.url.clone(),
schema: ValidFederationSchema::new(subgraph.schema.clone())?,
})?;
}
merger.merge(federation_subgraphs)
}
pub fn merge_federation_subgraphs(
subgraphs: ValidFederationSubgraphs,
) -> Result<MergeSuccess, MergeFailure> {
let mut merger = Merger::new();
merger.merge(subgraphs)
}
impl Merger {
fn new() -> Self {
Merger {
composition_hints: Vec::new(),
errors: Vec::new(),
needs_inaccessible: false,
interface_objects: IndexSet::default(),
}
}
fn merge(&mut self, subgraphs: ValidFederationSubgraphs) -> Result<MergeSuccess, MergeFailure> {
let mut subgraphs = subgraphs
.into_iter()
.map(|(_, subgraph)| subgraph)
.collect_vec();
subgraphs.sort_by(|s1, s2| s1.name.cmp(&s2.name));
let mut subgraphs_and_enum_values = Vec::new();
let mut enum_values = IndexSet::default();
for subgraph in &subgraphs {
let enum_value = match EnumValue::new(&subgraph.name) {
Ok(enum_value) => enum_value,
Err(err) => {
self.errors.push(err);
continue;
}
};
let enum_value = if enum_values.contains(&enum_value.to_string()) {
EnumValue::new(&format!("{}_{}", subgraph.name, enum_values.len()))
.expect("adding a suffix always works")
} else {
enum_value
};
enum_values.insert(enum_value.to_string());
subgraphs_and_enum_values.push((subgraph, enum_value))
}
if !self.errors.is_empty() {
return Err(MergeFailure {
schema: None,
composition_hints: self.composition_hints.to_owned(),
errors: self.errors.to_owned(),
});
}
let mut supergraph = Schema::new();
add_core_feature_link(&mut supergraph);
add_core_feature_join(&mut supergraph, &subgraphs_and_enum_values);
for (subgraph, subgraph_name) in &subgraphs_and_enum_values {
let sources = Arc::make_mut(&mut supergraph.sources);
for (key, source) in subgraph.schema.schema().sources.iter() {
sources.entry(*key).or_insert_with(|| source.clone());
}
self.merge_schema(&mut supergraph, subgraph);
let metadata = subgraph.schema.metadata();
let relevant_directives = DirectiveNames::for_metadata(&metadata);
for (type_name, ty) in &subgraph.schema.schema().types {
if ty.is_built_in() || !is_mergeable_type(type_name) {
continue;
}
match ty {
ExtendedType::Enum(value) => self.merge_enum_type(
&mut supergraph.types,
&relevant_directives,
subgraph_name,
type_name.clone(),
value,
),
ExtendedType::InputObject(value) => self.merge_input_object_type(
&mut supergraph.types,
&relevant_directives,
subgraph_name,
type_name.clone(),
value,
),
ExtendedType::Interface(value) => self.merge_interface_type(
&mut supergraph.types,
&relevant_directives,
subgraph_name,
type_name.clone(),
value,
),
ExtendedType::Object(value) => self.merge_object_type(
&mut supergraph.types,
&relevant_directives,
subgraph_name,
type_name.clone(),
value,
),
ExtendedType::Union(value) => self.merge_union_type(
&mut supergraph.types,
&relevant_directives,
subgraph_name,
type_name.clone(),
value,
),
ExtendedType::Scalar(value) => {
if !value.is_built_in() {
self.merge_scalar_type(
&mut supergraph.types,
&relevant_directives,
subgraph_name.clone(),
type_name.clone(),
value,
);
}
}
}
}
for (_, directive) in subgraph.schema.schema().directive_definitions.iter() {
if is_executable_directive(directive) {
merge_directive(&mut supergraph.directive_definitions, directive);
}
}
}
let implementers_map = supergraph.implementers_map();
self.add_interface_object_fields(&mut supergraph.types, implementers_map)?;
if self.needs_inaccessible {
add_core_feature_inaccessible(&mut supergraph);
}
if self.errors.is_empty() {
let supergraph = Valid::assume_valid(supergraph);
Ok(MergeSuccess {
schema: supergraph,
composition_hints: self.composition_hints.to_owned(),
})
} else {
Err(MergeFailure {
schema: Some(Box::new(supergraph)),
composition_hints: self.composition_hints.to_owned(),
errors: self.errors.to_owned(),
})
}
}
fn add_interface_object_fields(
&mut self,
types: &mut IndexMap<NamedType, ExtendedType>,
implementers_map: HashMap<Name, Implementers>,
) -> Result<(), MergeFailure> {
for interface_object_name in self.interface_objects.iter() {
let Some(ExtendedType::Interface(intf_def)) = types.get(interface_object_name) else {
return Err(MergeFailure {
schema: None,
composition_hints: self.composition_hints.to_owned(),
errors: vec![format!("Interface {} not found", interface_object_name)],
});
};
let fields = intf_def.fields.clone();
if let Some(implementers) = implementers_map.get(interface_object_name) {
for implementer in implementers.iter() {
types.entry(implementer.clone()).and_modify(|f| {
if let ExtendedType::Object(obj) = f {
let obj = obj.make_mut();
for (field_name, field_def) in fields.iter() {
let mut field_def = field_def.clone();
let field_def = field_def.make_mut();
field_def.directives = field_def
.directives
.iter()
.filter(|d| d.name != name!("join__field"))
.cloned()
.collect();
field_def.directives.push(Node::new(Directive {
name: name!("join__field"),
arguments: vec![],
}));
obj.fields
.entry(field_name.clone())
.or_insert(field_def.clone().into());
}
}
});
}
};
}
Ok(())
}
fn merge_descriptions<T: Eq + Clone>(&mut self, merged: &mut Option<T>, new: &Option<T>) {
match (&mut *merged, new) {
(_, None) => {}
(None, Some(_)) => merged.clone_from(new),
(Some(a), Some(b)) => {
if a != b {
self.composition_hints
.push(String::from("conflicting descriptions"));
}
}
}
}
fn merge_schema(&mut self, supergraph_schema: &mut Schema, subgraph: &ValidFederationSubgraph) {
let supergraph_def = &mut supergraph_schema.schema_definition.make_mut();
let subgraph_def = &subgraph.schema.schema().schema_definition;
self.merge_descriptions(&mut supergraph_def.description, &subgraph_def.description);
if subgraph_def.query.is_some() {
supergraph_def.query.clone_from(&subgraph_def.query);
}
if subgraph_def.mutation.is_some() {
supergraph_def.mutation.clone_from(&subgraph_def.mutation);
}
if subgraph_def.subscription.is_some() {
supergraph_def
.subscription
.clone_from(&subgraph_def.subscription);
}
}
fn merge_enum_type(
&mut self,
types: &mut IndexMap<NamedType, ExtendedType>,
metadata: &DirectiveNames,
subgraph_name: &EnumValue,
enum_name: NamedType,
enum_type: &Node<EnumType>,
) {
let existing_type = types
.entry(enum_name.clone())
.or_insert(copy_enum_type(enum_name, enum_type));
if let ExtendedType::Enum(e) = existing_type {
let join_type_directives =
join_type_applied_directive(subgraph_name.clone(), iter::empty(), false);
e.make_mut().directives.extend(join_type_directives);
self.add_inaccessible(
metadata,
&mut e.make_mut().directives,
&enum_type.directives,
);
self.merge_descriptions(&mut e.make_mut().description, &enum_type.description);
for (enum_value_name, enum_value) in enum_type.values.iter() {
let ev = e
.make_mut()
.values
.entry(enum_value_name.clone())
.or_insert(Component::new(EnumValueDefinition {
value: enum_value.value.clone(),
description: None,
directives: Default::default(),
}));
self.merge_descriptions(&mut ev.make_mut().description, &enum_value.description);
self.add_inaccessible(
metadata,
&mut ev.make_mut().directives,
&enum_value.directives,
);
ev.make_mut().directives.push(Node::new(Directive {
name: name!("join__enumValue"),
arguments: vec![
(Node::new(Argument {
name: name!("graph"),
value: Node::new(Value::Enum(subgraph_name.to_name())),
})),
],
}));
}
} else {
}
}
fn merge_input_object_type(
&mut self,
types: &mut IndexMap<NamedType, ExtendedType>,
directive_names: &DirectiveNames,
subgraph_name: &EnumValue,
input_object_name: NamedType,
input_object: &Node<InputObjectType>,
) {
let existing_type = types
.entry(input_object_name.clone())
.or_insert(copy_input_object_type(input_object_name, input_object));
if let ExtendedType::InputObject(obj) = existing_type {
let join_type_directives =
join_type_applied_directive(subgraph_name.clone(), iter::empty(), false);
let mutable_object = obj.make_mut();
mutable_object.directives.extend(join_type_directives);
self.add_inaccessible(
directive_names,
&mut mutable_object.directives,
&input_object.directives,
);
for (field_name, field) in input_object.fields.iter() {
let existing_field = mutable_object.fields.entry(field_name.clone());
let supergraph_field = match existing_field {
Vacant(i) => i.insert(Component::new(InputValueDefinition {
name: field.name.clone(),
description: field.description.clone(),
ty: field.ty.clone(),
default_value: field.default_value.clone(),
directives: Default::default(),
})),
Occupied(i) => {
i.into_mut()
}
};
self.add_inaccessible(
directive_names,
&mut supergraph_field.make_mut().directives,
&field.directives,
);
let join_field_directive = join_field_applied_directive(
subgraph_name,
None,
None,
false,
None,
Some(&field.ty),
);
supergraph_field
.make_mut()
.directives
.push(Node::new(join_field_directive));
}
} else {
}
}
fn merge_interface_type(
&mut self,
types: &mut IndexMap<NamedType, ExtendedType>,
directive_names: &DirectiveNames,
subgraph_name: &EnumValue,
interface_name: NamedType,
interface: &Node<InterfaceType>,
) {
let existing_type = types
.entry(interface_name.clone())
.or_insert(copy_interface_type(interface_name, interface));
if let ExtendedType::Interface(intf) = existing_type {
let key_directives = interface.directives.get_all(&directive_names.key);
let join_type_directives =
join_type_applied_directive(subgraph_name.clone(), key_directives, false);
let mutable_intf = intf.make_mut();
mutable_intf.directives.extend(join_type_directives);
self.add_inaccessible(
directive_names,
&mut mutable_intf.directives,
&interface.directives,
);
interface
.implements_interfaces
.iter()
.for_each(|intf_name| {
mutable_intf.implements_interfaces.insert(intf_name.clone());
let join_implements_directive =
join_implements_applied_directive(subgraph_name.clone(), intf_name);
mutable_intf.directives.push(join_implements_directive);
});
for (field_name, field) in interface.fields.iter() {
let existing_field = mutable_intf.fields.entry(field_name.clone());
let supergraph_field = match existing_field {
Occupied(f) => {
f.into_mut()
}
Vacant(f) => {
f.insert(Component::new(FieldDefinition {
name: field.name.clone(),
description: field.description.clone(),
arguments: vec![],
ty: field.ty.clone(),
directives: Default::default(),
}))
}
};
fields::merge_arguments(
field.arguments.iter(),
&mut supergraph_field.make_mut().arguments,
self,
directive_names,
);
self.merge_descriptions(
&mut supergraph_field.make_mut().description,
&field.description,
);
self.add_inaccessible(
directive_names,
&mut supergraph_field.make_mut().directives,
&field.directives,
);
let join_field_directive = join_field_applied_directive(
subgraph_name,
None,
None,
false,
None,
Some(&field.ty),
);
supergraph_field
.make_mut()
.directives
.push(Node::new(join_field_directive));
}
} else {
}
}
fn merge_object_type(
&mut self,
types: &mut IndexMap<NamedType, ExtendedType>,
directive_names: &DirectiveNames,
subgraph_name: &EnumValue,
object_name: NamedType,
object: &Node<ObjectType>,
) {
let is_interface_object = object.directives.has(&directive_names.interface_object);
let existing_type = types
.entry(object_name.clone())
.or_insert(copy_object_type_stub(
object_name,
object,
is_interface_object,
));
if let ExtendedType::Object(obj) = existing_type {
let key_directives = object.directives.get_all(&directive_names.key);
let join_type_directives =
join_type_applied_directive(subgraph_name.clone(), key_directives, false);
let mutable_object = obj.make_mut();
mutable_object.directives.extend(join_type_directives);
self.merge_descriptions(&mut mutable_object.description, &object.description);
self.add_inaccessible(
directive_names,
&mut mutable_object.directives,
&object.directives,
);
object.implements_interfaces.iter().for_each(|intf_name| {
mutable_object
.implements_interfaces
.insert(intf_name.clone());
let join_implements_directive =
join_implements_applied_directive(subgraph_name.clone(), intf_name);
mutable_object.directives.push(join_implements_directive);
});
for (field_name, field) in object.fields.iter() {
if field_name == "_service" || field_name == "_entities" {
continue;
}
let existing_field = mutable_object.fields.entry(field_name.clone());
let supergraph_field = match existing_field {
Occupied(f) => {
f.into_mut()
}
Vacant(f) => f.insert(Component::new(FieldDefinition {
name: field.name.clone(),
description: field.description.clone(),
arguments: vec![],
directives: Default::default(),
ty: field.ty.clone(),
})),
};
self.merge_descriptions(
&mut supergraph_field.make_mut().description,
&field.description,
);
self.add_inaccessible(
directive_names,
&mut supergraph_field.make_mut().directives,
&field.directives,
);
fields::merge_arguments(
field.arguments.iter(),
&mut supergraph_field.make_mut().arguments,
self,
directive_names,
);
let requires_directive_option = field
.directives
.get_all(&directive_names.requires)
.next()
.and_then(|p| directive_string_arg_value(p, &FEDERATION_FIELDS_ARGUMENT_NAME));
let provides_directive_option = field
.directives
.get_all(&directive_names.provides)
.next()
.and_then(|p| directive_string_arg_value(p, &FEDERATION_FIELDS_ARGUMENT_NAME));
let overrides_directive_option = field
.directives
.get_all(&directive_names.r#override)
.next()
.and_then(|p| {
let overrides_from =
directive_string_arg_value(p, &FEDERATION_FROM_ARGUMENT_NAME);
let overrides_label =
directive_string_arg_value(p, &FEDERATION_OVERRIDE_LABEL_ARGUMENT_NAME);
overrides_from.map(|from| (from, overrides_label))
});
let external_field = field
.directives
.get_all(&directive_names.external)
.next()
.is_some();
let join_field_directive = join_field_applied_directive(
subgraph_name,
requires_directive_option,
provides_directive_option,
external_field,
overrides_directive_option,
Some(&field.ty),
);
supergraph_field
.make_mut()
.directives
.push(Node::new(join_field_directive));
}
} else if let ExtendedType::Interface(intf) = existing_type {
self.interface_objects.insert(intf.name.clone());
let key_directives = object.directives.get_all(&directive_names.key);
let join_type_directives =
join_type_applied_directive(subgraph_name.clone(), key_directives, true);
let mutable_object = intf.make_mut();
mutable_object.directives.extend(join_type_directives);
self.merge_descriptions(&mut mutable_object.description, &object.description);
self.add_inaccessible(
directive_names,
&mut mutable_object.directives,
&object.directives,
);
for (field_name, field) in object.fields.iter() {
if field_name == "_service" || field_name == "_entities" {
continue;
}
let existing_field = mutable_object.fields.entry(field_name.clone());
let supergraph_field = match existing_field {
Occupied(f) => {
f.into_mut()
}
Vacant(f) => f.insert(Component::new(FieldDefinition {
name: field.name.clone(),
description: field.description.clone(),
arguments: vec![],
directives: Default::default(),
ty: field.ty.clone(),
})),
};
self.merge_descriptions(
&mut supergraph_field.make_mut().description,
&field.description,
);
self.add_inaccessible(
directive_names,
&mut supergraph_field.make_mut().directives,
&field.directives,
);
fields::merge_arguments(
field.arguments.iter(),
&mut supergraph_field.make_mut().arguments,
self,
directive_names,
);
let requires_directive_option = field
.directives
.get_all(&directive_names.requires)
.next()
.and_then(|p| directive_string_arg_value(p, &FEDERATION_FIELDS_ARGUMENT_NAME));
let provides_directive_option = field
.directives
.get_all(&directive_names.provides)
.next()
.and_then(|p| directive_string_arg_value(p, &FEDERATION_FIELDS_ARGUMENT_NAME));
let overrides_directive_option = field
.directives
.get_all(&directive_names.r#override)
.next()
.and_then(|p| {
let overrides_from =
directive_string_arg_value(p, &FEDERATION_FROM_ARGUMENT_NAME);
let overrides_label =
directive_string_arg_value(p, &FEDERATION_OVERRIDE_LABEL_ARGUMENT_NAME);
overrides_from.map(|from| (from, overrides_label))
});
let external_field = field
.directives
.get_all(&directive_names.external)
.next()
.is_some();
let join_field_directive = join_field_applied_directive(
subgraph_name,
requires_directive_option,
provides_directive_option,
external_field,
overrides_directive_option,
Some(&field.ty),
);
supergraph_field
.make_mut()
.directives
.push(Node::new(join_field_directive));
}
};
}
fn merge_union_type(
&mut self,
types: &mut IndexMap<NamedType, ExtendedType>,
directive_names: &DirectiveNames,
subgraph_name: &EnumValue,
union_name: NamedType,
union: &Node<UnionType>,
) {
let existing_type = types
.entry(union_name.clone())
.or_insert(copy_union_type(union_name, union.description.clone()));
if let ExtendedType::Union(u) = existing_type {
let join_type_directives =
join_type_applied_directive(subgraph_name.clone(), iter::empty(), false);
u.make_mut().directives.extend(join_type_directives);
self.add_inaccessible(
directive_names,
&mut u.make_mut().directives,
&union.directives,
);
for union_member in union.members.iter() {
u.make_mut().members.insert(union_member.clone());
u.make_mut().directives.push(Component::new(Directive {
name: name!("join__unionMember"),
arguments: vec![
Node::new(Argument {
name: name!("graph"),
value: Node::new(Value::Enum(subgraph_name.to_name())),
}),
Node::new(Argument {
name: name!("member"),
value: union_member.as_str().into(),
}),
],
}));
}
}
}
fn merge_scalar_type(
&mut self,
types: &mut IndexMap<Name, ExtendedType>,
directive_names: &DirectiveNames,
subgraph_name: EnumValue,
scalar_name: NamedType,
ty: &Node<ScalarType>,
) {
let existing_type = types
.entry(scalar_name.clone())
.or_insert(copy_scalar_type(scalar_name, ty));
if let ExtendedType::Scalar(s) = existing_type {
let join_type_directives =
join_type_applied_directive(subgraph_name, iter::empty(), false);
s.make_mut().directives.extend(join_type_directives);
self.add_inaccessible(
directive_names,
&mut s.make_mut().directives,
&ty.directives,
);
} else {
}
}
fn add_inaccessible<I>(
&mut self,
directive_names: &DirectiveNames,
new_directives: &mut Vec<I>,
original_directives: &[I],
) where
I: AsRef<Directive> + From<Directive> + Clone,
{
if original_directives
.iter()
.any(|d| d.as_ref().name == directive_names.inaccessible)
&& !new_directives
.iter()
.any(|d| d.as_ref().name == INACCESSIBLE_DIRECTIVE_NAME_IN_SPEC)
{
self.needs_inaccessible = true;
new_directives.push(
Directive {
name: INACCESSIBLE_DIRECTIVE_NAME_IN_SPEC,
arguments: vec![],
}
.into(),
);
}
}
}
struct DirectiveNames {
key: Name,
requires: Name,
provides: Name,
external: Name,
interface_object: Name,
r#override: Name,
inaccessible: Name,
}
impl DirectiveNames {
fn for_metadata(metadata: &Option<&LinksMetadata>) -> Self {
let federation_identity =
metadata.and_then(|m| m.by_identity.get(&Identity::federation_identity()));
let key = federation_identity
.map(|link| link.directive_name_in_schema(&FEDERATION_KEY_DIRECTIVE_NAME_IN_SPEC))
.unwrap_or(FEDERATION_KEY_DIRECTIVE_NAME_IN_SPEC);
let requires = federation_identity
.map(|link| link.directive_name_in_schema(&FEDERATION_REQUIRES_DIRECTIVE_NAME_IN_SPEC))
.unwrap_or(FEDERATION_REQUIRES_DIRECTIVE_NAME_IN_SPEC);
let provides = federation_identity
.map(|link| link.directive_name_in_schema(&FEDERATION_PROVIDES_DIRECTIVE_NAME_IN_SPEC))
.unwrap_or(FEDERATION_PROVIDES_DIRECTIVE_NAME_IN_SPEC);
let external = federation_identity
.map(|link| link.directive_name_in_schema(&FEDERATION_EXTERNAL_DIRECTIVE_NAME_IN_SPEC))
.unwrap_or(FEDERATION_EXTERNAL_DIRECTIVE_NAME_IN_SPEC);
let interface_object = federation_identity
.map(|link| {
link.directive_name_in_schema(&FEDERATION_INTERFACEOBJECT_DIRECTIVE_NAME_IN_SPEC)
})
.unwrap_or(FEDERATION_INTERFACEOBJECT_DIRECTIVE_NAME_IN_SPEC);
let r#override = federation_identity
.map(|link| link.directive_name_in_schema(&FEDERATION_OVERRIDE_DIRECTIVE_NAME_IN_SPEC))
.unwrap_or(FEDERATION_OVERRIDE_DIRECTIVE_NAME_IN_SPEC);
let inaccessible = federation_identity
.map(|link| link.directive_name_in_schema(&INACCESSIBLE_DIRECTIVE_NAME_IN_SPEC))
.unwrap_or(INACCESSIBLE_DIRECTIVE_NAME_IN_SPEC);
Self {
key,
requires,
provides,
external,
interface_object,
r#override,
inaccessible,
}
}
}
const EXECUTABLE_DIRECTIVE_LOCATIONS: [DirectiveLocation; 8] = [
DirectiveLocation::Query,
DirectiveLocation::Mutation,
DirectiveLocation::Subscription,
DirectiveLocation::Field,
DirectiveLocation::FragmentDefinition,
DirectiveLocation::FragmentSpread,
DirectiveLocation::InlineFragment,
DirectiveLocation::VariableDefinition,
];
fn is_executable_directive(directive: &Node<DirectiveDefinition>) -> bool {
directive
.locations
.iter()
.any(|loc| EXECUTABLE_DIRECTIVE_LOCATIONS.contains(loc))
}
const FEDERATION_TYPES: [&str; 4] = ["_Any", "_Entity", "_Service", "@key"];
fn is_mergeable_type(type_name: &str) -> bool {
if type_name.starts_with("federation__") || type_name.starts_with("link__") {
return false;
}
!FEDERATION_TYPES.contains(&type_name)
}
fn copy_scalar_type(scalar_name: Name, scalar_type: &Node<ScalarType>) -> ExtendedType {
ExtendedType::Scalar(Node::new(ScalarType {
description: scalar_type.description.clone(),
name: scalar_name,
directives: Default::default(),
}))
}
fn copy_enum_type(enum_name: Name, enum_type: &Node<EnumType>) -> ExtendedType {
ExtendedType::Enum(Node::new(EnumType {
description: enum_type.description.clone(),
name: enum_name,
directives: Default::default(),
values: IndexMap::default(),
}))
}
fn copy_input_object_type(
input_object_name: Name,
input_object: &Node<InputObjectType>,
) -> ExtendedType {
let mut new_input_object = InputObjectType {
description: input_object.description.clone(),
name: input_object_name,
directives: Default::default(),
fields: IndexMap::default(),
};
for (field_name, input_field) in input_object.fields.iter() {
new_input_object.fields.insert(
field_name.clone(),
Component::new(InputValueDefinition {
name: input_field.name.clone(),
description: input_field.description.clone(),
directives: Default::default(),
ty: input_field.ty.clone(),
default_value: input_field.default_value.clone(),
}),
);
}
ExtendedType::InputObject(Node::new(new_input_object))
}
fn copy_interface_type(interface_name: Name, interface: &Node<InterfaceType>) -> ExtendedType {
let new_interface = InterfaceType {
description: interface.description.clone(),
name: interface_name,
directives: Default::default(),
fields: copy_fields(interface.fields.iter()),
implements_interfaces: interface.implements_interfaces.clone(),
};
ExtendedType::Interface(Node::new(new_interface))
}
fn copy_object_type_stub(
object_name: Name,
object: &Node<ObjectType>,
is_interface_object: bool,
) -> ExtendedType {
if is_interface_object {
let new_interface = InterfaceType {
description: object.description.clone(),
name: object_name,
directives: Default::default(),
fields: copy_fields(object.fields.iter()),
implements_interfaces: object.implements_interfaces.clone(),
};
ExtendedType::Interface(Node::new(new_interface))
} else {
let new_object = ObjectType {
description: object.description.clone(),
name: object_name,
directives: Default::default(),
fields: copy_fields(object.fields.iter()),
implements_interfaces: object.implements_interfaces.clone(),
};
ExtendedType::Object(Node::new(new_object))
}
}
fn copy_fields(
fields_to_copy: Iter<Name, Component<FieldDefinition>>,
) -> IndexMap<Name, Component<FieldDefinition>> {
let mut new_fields: IndexMap<Name, Component<FieldDefinition>> = IndexMap::default();
for (field_name, field) in fields_to_copy {
if field_name == "_service" || field_name == "_entities" {
continue;
}
let args: Vec<Node<InputValueDefinition>> = field
.arguments
.iter()
.map(|a| {
Node::new(InputValueDefinition {
name: a.name.clone(),
description: a.description.clone(),
directives: Default::default(),
ty: a.ty.clone(),
default_value: a.default_value.clone(),
})
})
.collect();
let new_field = Component::new(FieldDefinition {
name: field.name.clone(),
description: field.description.clone(),
directives: Default::default(),
arguments: args,
ty: field.ty.clone(),
});
new_fields.insert(field_name.clone(), new_field);
}
new_fields
}
fn copy_union_type(union_name: Name, description: Option<Node<str>>) -> ExtendedType {
ExtendedType::Union(Node::new(UnionType {
description,
name: union_name,
directives: Default::default(),
members: IndexSet::default(),
}))
}
fn join_type_applied_directive<'a>(
subgraph_name: EnumValue,
key_directives: impl Iterator<Item = &'a Component<Directive>> + Sized,
is_interface_object: bool,
) -> Vec<Component<Directive>> {
let mut join_type_directive = Directive {
name: name!("join__type"),
arguments: vec![Node::new(Argument {
name: name!("graph"),
value: Node::new(Value::Enum(subgraph_name.into())),
})],
};
if is_interface_object {
join_type_directive.arguments.push(Node::new(Argument {
name: name!("isInterfaceObject"),
value: Node::new(Value::Boolean(is_interface_object)),
}));
}
let mut result = vec![];
for key_directive in key_directives {
let mut join_type_directive_with_key = join_type_directive.clone();
let field_set = directive_string_arg_value(key_directive, &name!("fields")).unwrap();
join_type_directive_with_key
.arguments
.push(Node::new(Argument {
name: name!("key"),
value: field_set.into(),
}));
let resolvable =
directive_bool_arg_value(key_directive, &name!("resolvable")).unwrap_or(&true);
if !resolvable {
join_type_directive_with_key
.arguments
.push(Node::new(Argument {
name: name!("resolvable"),
value: Node::new(Value::Boolean(false)),
}));
}
result.push(join_type_directive_with_key)
}
if result.is_empty() {
result.push(join_type_directive)
}
result
.into_iter()
.map(Component::new)
.collect::<Vec<Component<Directive>>>()
}
fn join_implements_applied_directive(
subgraph_name: EnumValue,
intf_name: &Name,
) -> Component<Directive> {
Component::new(Directive {
name: name!("join__implements"),
arguments: vec![
Node::new(Argument {
name: name!("graph"),
value: Node::new(Value::Enum(subgraph_name.into())),
}),
Node::new(Argument {
name: name!("interface"),
value: intf_name.as_str().into(),
}),
],
})
}
fn directive_arg_value<'a>(directive: &'a Directive, arg_name: &Name) -> Option<&'a Value> {
directive
.arguments
.iter()
.find(|arg| arg.name == *arg_name)
.map(|arg| arg.value.as_ref())
}
fn directive_string_arg_value<'a>(directive: &'a Directive, arg_name: &Name) -> Option<&'a str> {
match directive_arg_value(directive, arg_name) {
Some(Value::String(value)) => Some(value),
_ => None,
}
}
fn directive_bool_arg_value<'a>(directive: &'a Directive, arg_name: &Name) -> Option<&'a bool> {
match directive_arg_value(directive, arg_name) {
Some(Value::Boolean(value)) => Some(value),
_ => None,
}
}
fn add_core_feature_link(supergraph: &mut Schema) {
supergraph
.schema_definition
.make_mut()
.directives
.push(Component::new(Directive {
name: name!("link"),
arguments: vec![Node::new(Argument {
name: name!("url"),
value: Node::new("https://specs.apollo.dev/link/v1.0".into()),
})],
}));
let (name, link_purpose_enum) = link_purpose_enum_type();
supergraph.types.insert(name, link_purpose_enum.into());
let link_import_name = name!("link__Import");
let link_import_scalar = ExtendedType::Scalar(Node::new(ScalarType {
directives: Default::default(),
name: link_import_name.clone(),
description: None,
}));
supergraph
.types
.insert(link_import_name, link_import_scalar);
let link_directive_definition = link_directive_definition();
supergraph
.directive_definitions
.insert(name!("link"), Node::new(link_directive_definition));
}
fn link_directive_definition() -> DirectiveDefinition {
DirectiveDefinition {
name: name!("link"),
description: None,
arguments: vec![
Node::new(InputValueDefinition {
name: name!("url"),
description: None,
directives: Default::default(),
ty: ty!(String).into(),
default_value: None,
}),
Node::new(InputValueDefinition {
name: name!("as"),
description: None,
directives: Default::default(),
ty: ty!(String).into(),
default_value: None,
}),
Node::new(InputValueDefinition {
name: name!("for"),
description: None,
directives: Default::default(),
ty: ty!(link__Purpose).into(),
default_value: None,
}),
Node::new(InputValueDefinition {
name: name!("import"),
description: None,
directives: Default::default(),
ty: ty!([link__Import]).into(),
default_value: None,
}),
],
locations: vec![DirectiveLocation::Schema],
repeatable: true,
}
}
fn link_purpose_enum_type() -> (Name, EnumType) {
let link_purpose_name = name!("link__Purpose");
let mut link_purpose_enum = EnumType {
description: None,
name: link_purpose_name.clone(),
directives: Default::default(),
values: IndexMap::default(),
};
let link_purpose_security_value = EnumValueDefinition {
description: Some(
r"SECURITY features provide metadata necessary to securely resolve fields.".into(),
),
directives: Default::default(),
value: name!("SECURITY"),
};
let link_purpose_execution_value = EnumValueDefinition {
description: Some(
r"EXECUTION features provide metadata necessary for operation execution.".into(),
),
directives: Default::default(),
value: name!("EXECUTION"),
};
link_purpose_enum.values.insert(
link_purpose_security_value.value.clone(),
Component::new(link_purpose_security_value),
);
link_purpose_enum.values.insert(
link_purpose_execution_value.value.clone(),
Component::new(link_purpose_execution_value),
);
(link_purpose_name, link_purpose_enum)
}
fn add_core_feature_join(
supergraph: &mut Schema,
subgraphs_and_enum_values: &Vec<(&ValidFederationSubgraph, EnumValue)>,
) {
supergraph
.schema_definition
.make_mut()
.directives
.push(Component::new(Directive {
name: name!("link"),
arguments: vec![
Node::new(Argument {
name: name!("url"),
value: "https://specs.apollo.dev/join/v0.5".into(),
}),
Node::new(Argument {
name: name!("for"),
value: Node::new(Value::Enum(name!("EXECUTION"))),
}),
],
}));
let join_field_set_name = name!("join__FieldSet");
let join_field_set_scalar = ExtendedType::Scalar(Node::new(ScalarType {
directives: Default::default(),
name: join_field_set_name.clone(),
description: None,
}));
supergraph
.types
.insert(join_field_set_name, join_field_set_scalar);
let join_field_value_name = name!("join__FieldValue");
let join_field_value_scalar = ExtendedType::Scalar(Node::new(ScalarType {
directives: Default::default(),
name: join_field_value_name.clone(),
description: None,
}));
supergraph
.types
.insert(join_field_value_name, join_field_value_scalar);
let join_context_argument_name = name!("join__ContextArgument");
let join_context_argument_input = ExtendedType::InputObject(Node::new(InputObjectType {
description: None,
name: join_context_argument_name.clone(),
directives: Default::default(),
fields: vec![
(
name!("name"),
Component::new(InputValueDefinition {
name: name!("name"),
description: None,
directives: Default::default(),
ty: ty!(String!).into(),
default_value: None,
}),
),
(
name!("type"),
Component::new(InputValueDefinition {
name: name!("type"),
description: None,
directives: Default::default(),
ty: ty!(String!).into(),
default_value: None,
}),
),
(
name!("context"),
Component::new(InputValueDefinition {
name: name!("context"),
description: None,
directives: Default::default(),
ty: ty!(String!).into(),
default_value: None,
}),
),
(
name!("selection"),
Component::new(InputValueDefinition {
name: name!("selection"),
description: None,
directives: Default::default(),
ty: ty!(join__FieldValue!).into(),
default_value: None,
}),
),
]
.into_iter()
.collect(),
}));
supergraph
.types
.insert(join_context_argument_name, join_context_argument_input);
let join_graph_directive_definition = join_graph_directive_definition();
supergraph.directive_definitions.insert(
join_graph_directive_definition.name.clone(),
Node::new(join_graph_directive_definition),
);
let join_type_directive_definition = join_type_directive_definition();
supergraph.directive_definitions.insert(
join_type_directive_definition.name.clone(),
Node::new(join_type_directive_definition),
);
let join_field_directive_definition = join_field_directive_definition();
supergraph.directive_definitions.insert(
join_field_directive_definition.name.clone(),
Node::new(join_field_directive_definition),
);
let join_implements_directive_definition = join_implements_directive_definition();
supergraph.directive_definitions.insert(
join_implements_directive_definition.name.clone(),
Node::new(join_implements_directive_definition),
);
let join_union_member_directive_definition = join_union_member_directive_definition();
supergraph.directive_definitions.insert(
join_union_member_directive_definition.name.clone(),
Node::new(join_union_member_directive_definition),
);
let join_enum_value_directive_definition = join_enum_value_directive_definition();
supergraph.directive_definitions.insert(
join_enum_value_directive_definition.name.clone(),
Node::new(join_enum_value_directive_definition),
);
let join_directive_arguments_name = name!("join__DirectiveArguments");
let join_directive_arguments_scalar = ExtendedType::Scalar(Node::new(ScalarType {
directives: Default::default(),
name: join_directive_arguments_name.clone(),
description: None,
}));
supergraph.types.insert(
join_directive_arguments_name,
join_directive_arguments_scalar,
);
let join_directive_directive_definition = join_directive_directive_definition();
supergraph.directive_definitions.insert(
join_directive_directive_definition.name.clone(),
Node::new(join_directive_directive_definition),
);
let (name, join_graph_enum_type) = join_graph_enum_type(subgraphs_and_enum_values);
supergraph.types.insert(name, join_graph_enum_type.into());
}
fn join_enum_value_directive_definition() -> DirectiveDefinition {
DirectiveDefinition {
name: name!("join__enumValue"),
description: None,
arguments: vec![Node::new(InputValueDefinition {
name: name!("graph"),
description: None,
directives: Default::default(),
ty: ty!(join__Graph!).into(),
default_value: None,
})],
locations: vec![DirectiveLocation::EnumValue],
repeatable: true,
}
}
fn join_directive_directive_definition() -> DirectiveDefinition {
DirectiveDefinition {
name: name!("join__directive"),
description: None,
arguments: vec![
Node::new(InputValueDefinition {
name: name!("graphs"),
description: None,
directives: Default::default(),
ty: ty!([join__Graph!]).into(),
default_value: None,
}),
Node::new(InputValueDefinition {
name: name!("name"),
description: None,
directives: Default::default(),
ty: ty!(String!).into(),
default_value: None,
}),
Node::new(InputValueDefinition {
name: name!("args"),
description: None,
directives: Default::default(),
ty: ty!(join__DirectiveArguments!).into(),
default_value: None,
}),
],
locations: vec![
DirectiveLocation::Schema,
DirectiveLocation::Object,
DirectiveLocation::Interface,
DirectiveLocation::FieldDefinition,
],
repeatable: true,
}
}
fn join_field_directive_definition() -> DirectiveDefinition {
DirectiveDefinition {
name: name!("join__field"),
description: None,
arguments: vec![
Node::new(InputValueDefinition {
name: name!("graph"),
description: None,
directives: Default::default(),
ty: ty!(join__Graph).into(),
default_value: None,
}),
Node::new(InputValueDefinition {
name: name!("requires"),
description: None,
directives: Default::default(),
ty: ty!(join__FieldSet).into(),
default_value: None,
}),
Node::new(InputValueDefinition {
name: name!("provides"),
description: None,
directives: Default::default(),
ty: ty!(join__FieldSet).into(),
default_value: None,
}),
Node::new(InputValueDefinition {
name: name!("type"),
description: None,
directives: Default::default(),
ty: ty!(String).into(),
default_value: None,
}),
Node::new(InputValueDefinition {
name: name!("external"),
description: None,
directives: Default::default(),
ty: ty!(Boolean).into(),
default_value: None,
}),
Node::new(InputValueDefinition {
name: name!("override"),
description: None,
directives: Default::default(),
ty: ty!(String).into(),
default_value: None,
}),
Node::new(InputValueDefinition {
name: JOIN_OVERRIDE_LABEL_ARGUMENT_NAME,
description: None,
directives: Default::default(),
ty: ty!(String).into(),
default_value: None,
}),
Node::new(InputValueDefinition {
name: name!("usedOverridden"),
description: None,
directives: Default::default(),
ty: ty!(Boolean).into(),
default_value: None,
}),
Node::new(InputValueDefinition {
name: name!("contextArguments"),
description: None,
directives: Default::default(),
ty: ty!([join__ContextArgument!]).into(),
default_value: None,
}),
],
locations: vec![
DirectiveLocation::FieldDefinition,
DirectiveLocation::InputFieldDefinition,
],
repeatable: true,
}
}
fn join_field_applied_directive(
subgraph_name: &EnumValue,
requires: Option<&str>,
provides: Option<&str>,
external: bool,
overrides: Option<(&str, Option<&str>)>, r#type: Option<&Type>,
) -> Directive {
let mut join_field_directive = Directive {
name: name!("join__field"),
arguments: vec![Node::new(Argument {
name: name!("graph"),
value: Node::new(Value::Enum(subgraph_name.to_name())),
})],
};
if let Some(required_fields) = requires {
join_field_directive.arguments.push(Node::new(Argument {
name: name!("requires"),
value: required_fields.into(),
}));
}
if let Some(provided_fields) = provides {
join_field_directive.arguments.push(Node::new(Argument {
name: name!("provides"),
value: provided_fields.into(),
}));
}
if external {
join_field_directive.arguments.push(Node::new(Argument {
name: name!("external"),
value: external.into(),
}));
}
if let Some((from, label)) = overrides {
join_field_directive.arguments.push(Node::new(Argument {
name: name!("override"),
value: Node::new(Value::String(from.to_string())),
}));
if let Some(label) = label {
join_field_directive.arguments.push(Node::new(Argument {
name: name!("overrideLabel"),
value: Node::new(Value::String(label.to_string())),
}));
}
}
if let Some(r#type) = r#type {
join_field_directive.arguments.push(Node::new(Argument {
name: name!("type"),
value: r#type.to_string().into(),
}));
}
join_field_directive
}
fn join_graph_directive_definition() -> DirectiveDefinition {
DirectiveDefinition {
name: name!("join__graph"),
description: None,
arguments: vec![
Node::new(InputValueDefinition {
name: name!("name"),
description: None,
directives: Default::default(),
ty: ty!(String!).into(),
default_value: None,
}),
Node::new(InputValueDefinition {
name: name!("url"),
description: None,
directives: Default::default(),
ty: ty!(String!).into(),
default_value: None,
}),
],
locations: vec![DirectiveLocation::EnumValue],
repeatable: false,
}
}
fn join_implements_directive_definition() -> DirectiveDefinition {
DirectiveDefinition {
name: name!("join__implements"),
description: None,
arguments: vec![
Node::new(InputValueDefinition {
name: name!("graph"),
description: None,
directives: Default::default(),
ty: ty!(join__Graph!).into(),
default_value: None,
}),
Node::new(InputValueDefinition {
name: name!("interface"),
description: None,
directives: Default::default(),
ty: ty!(String!).into(),
default_value: None,
}),
],
locations: vec![DirectiveLocation::Interface, DirectiveLocation::Object],
repeatable: true,
}
}
fn join_type_directive_definition() -> DirectiveDefinition {
DirectiveDefinition {
name: name!("join__type"),
description: None,
arguments: vec![
Node::new(InputValueDefinition {
name: name!("graph"),
description: None,
directives: Default::default(),
ty: ty!(join__Graph!).into(),
default_value: None,
}),
Node::new(InputValueDefinition {
name: name!("key"),
description: None,
directives: Default::default(),
ty: ty!(join__FieldSet).into(),
default_value: None,
}),
Node::new(InputValueDefinition {
name: name!("extension"),
description: None,
directives: Default::default(),
ty: ty!(Boolean!).into(),
default_value: Some(Node::new(Value::Boolean(false))),
}),
Node::new(InputValueDefinition {
name: name!("resolvable"),
description: None,
directives: Default::default(),
ty: ty!(Boolean!).into(),
default_value: Some(Node::new(Value::Boolean(true))),
}),
Node::new(InputValueDefinition {
name: name!("isInterfaceObject"),
description: None,
directives: Default::default(),
ty: ty!(Boolean!).into(),
default_value: Some(Node::new(Value::Boolean(false))),
}),
],
locations: vec![
DirectiveLocation::Enum,
DirectiveLocation::InputObject,
DirectiveLocation::Interface,
DirectiveLocation::Object,
DirectiveLocation::Scalar,
DirectiveLocation::Union,
],
repeatable: true,
}
}
fn join_union_member_directive_definition() -> DirectiveDefinition {
DirectiveDefinition {
name: name!("join__unionMember"),
description: None,
arguments: vec![
Node::new(InputValueDefinition {
name: name!("graph"),
description: None,
directives: Default::default(),
ty: ty!(join__Graph!).into(),
default_value: None,
}),
Node::new(InputValueDefinition {
name: name!("member"),
description: None,
directives: Default::default(),
ty: ty!(String!).into(),
default_value: None,
}),
],
locations: vec![DirectiveLocation::Union],
repeatable: true,
}
}
fn join_graph_enum_type(
subgraphs_and_enum_values: &Vec<(&ValidFederationSubgraph, EnumValue)>,
) -> (Name, EnumType) {
let join_graph_enum_name = name!("join__Graph");
let mut join_graph_enum_type = EnumType {
description: None,
name: join_graph_enum_name.clone(),
directives: Default::default(),
values: IndexMap::default(),
};
for (s, subgraph_name) in subgraphs_and_enum_values {
let join_graph_applied_directive = Directive {
name: name!("join__graph"),
arguments: vec![
(Node::new(Argument {
name: name!("name"),
value: s.name.as_str().into(),
})),
(Node::new(Argument {
name: name!("url"),
value: s.url.as_str().into(),
})),
],
};
let graph = EnumValueDefinition {
description: None,
directives: DirectiveList(vec![Node::new(join_graph_applied_directive)]),
value: subgraph_name.to_name(),
};
join_graph_enum_type
.values
.insert(graph.value.clone(), Component::new(graph));
}
(join_graph_enum_name, join_graph_enum_type)
}
fn add_core_feature_inaccessible(supergraph: &mut Schema) {
let spec = InaccessibleSpecDefinition::new(
Version { major: 0, minor: 2 },
Version { major: 2, minor: 0 },
);
supergraph
.schema_definition
.make_mut()
.directives
.push(Component::new(Directive {
name: name!("link"),
arguments: vec![
Node::new(Argument {
name: name!("url"),
value: spec.to_string().into(),
}),
Node::new(Argument {
name: name!("for"),
value: Node::new(Value::Enum(name!("SECURITY"))),
}),
],
}));
supergraph.directive_definitions.insert(
INACCESSIBLE_DIRECTIVE_NAME_IN_SPEC,
Node::new(DirectiveDefinition {
name: INACCESSIBLE_DIRECTIVE_NAME_IN_SPEC,
description: None,
arguments: vec![],
locations: vec![
DirectiveLocation::FieldDefinition,
DirectiveLocation::Object,
DirectiveLocation::Interface,
DirectiveLocation::Union,
DirectiveLocation::ArgumentDefinition,
DirectiveLocation::Scalar,
DirectiveLocation::Enum,
DirectiveLocation::EnumValue,
DirectiveLocation::InputObject,
DirectiveLocation::InputFieldDefinition,
],
repeatable: false,
}),
);
}
fn merge_directive(
supergraph_directives: &mut IndexMap<Name, Node<DirectiveDefinition>>,
directive: &Node<DirectiveDefinition>,
) {
if !supergraph_directives.contains_key(&directive.name.clone()) {
supergraph_directives.insert(directive.name.clone(), directive.clone());
}
}
#[cfg(test)]
mod tests;