use std::rc::Rc;
use std::sync::LazyLock;
use apollo_compiler::Name;
use apollo_compiler::Node;
use apollo_compiler::ast::Directive;
use apollo_compiler::ast::DirectiveDefinition;
use apollo_compiler::ast::DirectiveLocation;
use apollo_compiler::ast::Type;
use apollo_compiler::collections::IndexMap;
use apollo_compiler::schema::Value;
use apollo_compiler::ty;
use super::federation_spec_definition::get_federation_spec_definition_from_subgraph;
use crate::bail;
use crate::error::FederationError;
use crate::internal_error;
use crate::link::Purpose;
use crate::link::argument::directive_required_string_argument;
use crate::link::federation_spec_definition::FEDERATION_CONTEXT_DIRECTIVE_NAME_IN_SPEC;
use crate::link::federation_spec_definition::FEDERATION_FIELD_ARGUMENT_NAME;
use crate::link::federation_spec_definition::FEDERATION_FROM_CONTEXT_DIRECTIVE_NAME_IN_SPEC;
use crate::link::federation_spec_definition::FEDERATION_NAME_ARGUMENT_NAME;
use crate::link::spec::Identity;
use crate::link::spec::Url;
use crate::link::spec::Version;
use crate::link::spec_definition::SpecDefinition;
use crate::link::spec_definition::SpecDefinitions;
use crate::schema::FederationSchema;
use crate::schema::type_and_directive_specification::ArgumentSpecification;
use crate::schema::type_and_directive_specification::DirectiveArgumentSpecification;
use crate::schema::type_and_directive_specification::DirectiveCompositionOptions;
use crate::schema::type_and_directive_specification::DirectiveSpecification;
use crate::schema::type_and_directive_specification::ScalarTypeSpecification;
use crate::schema::type_and_directive_specification::TypeAndDirectiveSpecification;
use crate::subgraph::spec::CONTEXTFIELDVALUE_SCALAR_NAME;
use crate::subgraph::typestate::Subgraph;
use crate::subgraph::typestate::Validated;
pub(crate) struct ContextDirectiveArguments<'doc> {
pub(crate) name: &'doc str,
}
#[derive(Clone)]
pub(crate) struct ContextSpecDefinition {
url: Url,
minimum_federation_version: Version,
}
impl ContextSpecDefinition {
pub(crate) fn new(version: Version, minimum_federation_version: Version) -> Self {
Self {
url: Url {
identity: Identity::context_identity(),
version,
},
minimum_federation_version,
}
}
fn static_argument_transform(
subgraph: &Subgraph<Validated>,
args: IndexMap<Name, Value>,
) -> IndexMap<Name, Value> {
let subgraph_name = &subgraph.name;
args.into_iter()
.map(|(key, value)| {
if *key.as_str() == *FEDERATION_NAME_ARGUMENT_NAME.as_str() {
(
key,
Value::String(format!("{}__{}", subgraph_name, value.as_str().unwrap())),
)
} else {
(key, value)
}
})
.collect()
}
pub(crate) fn context_directive_definition<'schema>(
&self,
schema: &'schema FederationSchema,
) -> Result<&'schema Node<DirectiveDefinition>, FederationError> {
self.directive_definition(schema, &FEDERATION_CONTEXT_DIRECTIVE_NAME_IN_SPEC)?
.ok_or_else(|| internal_error!("Unexpectedly could not find context spec in schema"))
}
pub(crate) fn context_directive_arguments<'doc>(
&self,
application: &'doc Node<Directive>,
) -> Result<ContextDirectiveArguments<'doc>, FederationError> {
Ok(ContextDirectiveArguments {
name: directive_required_string_argument(application, &FEDERATION_NAME_ARGUMENT_NAME)?,
})
}
fn field_argument_specification() -> DirectiveArgumentSpecification {
DirectiveArgumentSpecification {
base_spec: ArgumentSpecification {
name: FEDERATION_FIELD_ARGUMENT_NAME,
get_type: |_schema, link| {
let Some(link) = link else {
bail!(
"Type {FEDERATION_FIELD_ARGUMENT_NAME} shouldn't be added without being attached to a @link spec"
)
};
let type_name = link.type_name_in_schema(&CONTEXTFIELDVALUE_SCALAR_NAME);
Ok(Type::nullable(Type::Named(type_name)))
},
default_value: None,
},
composition_strategy: None,
}
}
fn for_federation_schema(schema: &FederationSchema) -> Option<&'static Self> {
let link = schema
.metadata()?
.for_identity(&Identity::context_identity())?;
CONTEXT_VERSIONS.find(&link.url.version)
}
#[allow(dead_code)]
pub(crate) fn context_directive_name(
schema: &FederationSchema,
) -> Result<Option<Name>, FederationError> {
if let Some(spec) = Self::for_federation_schema(schema) {
spec.directive_name_in_schema(schema, &FEDERATION_CONTEXT_DIRECTIVE_NAME_IN_SPEC)
} else if let Ok(fed_spec) = get_federation_spec_definition_from_subgraph(schema) {
fed_spec.directive_name_in_schema(schema, &FEDERATION_CONTEXT_DIRECTIVE_NAME_IN_SPEC)
} else {
Ok(None)
}
}
#[allow(dead_code)]
pub(crate) fn from_context_directive_name(
schema: &FederationSchema,
) -> Result<Option<Name>, FederationError> {
if let Some(spec) = Self::for_federation_schema(schema) {
spec.directive_name_in_schema(schema, &FEDERATION_FROM_CONTEXT_DIRECTIVE_NAME_IN_SPEC)
} else if let Ok(fed_spec) = get_federation_spec_definition_from_subgraph(schema) {
fed_spec
.directive_name_in_schema(schema, &FEDERATION_FROM_CONTEXT_DIRECTIVE_NAME_IN_SPEC)
} else {
Ok(None)
}
}
}
impl SpecDefinition for ContextSpecDefinition {
fn url(&self) -> &Url {
&self.url
}
fn directive_specs(&self) -> Vec<Box<dyn TypeAndDirectiveSpecification>> {
let context_spec = DirectiveSpecification::new(
FEDERATION_CONTEXT_DIRECTIVE_NAME_IN_SPEC,
&[DirectiveArgumentSpecification {
base_spec: ArgumentSpecification {
name: FEDERATION_NAME_ARGUMENT_NAME,
get_type: |_, _| Ok(ty!(String!)),
default_value: None,
},
composition_strategy: None,
}],
true,
&[
DirectiveLocation::Interface,
DirectiveLocation::Object,
DirectiveLocation::Union,
],
Some(DirectiveCompositionOptions {
supergraph_specification: &|v| CONTEXT_VERSIONS.get_dyn_minimum_required_version(v),
static_argument_transform: Some(Rc::new(Self::static_argument_transform)),
use_join_directive: false,
}),
);
let from_context_spec = DirectiveSpecification::new(
FEDERATION_FROM_CONTEXT_DIRECTIVE_NAME_IN_SPEC,
&[Self::field_argument_specification()],
false,
&[DirectiveLocation::ArgumentDefinition],
None,
);
vec![Box::new(context_spec), Box::new(from_context_spec)]
}
fn type_specs(&self) -> Vec<Box<dyn TypeAndDirectiveSpecification>> {
vec![Box::new(ScalarTypeSpecification {
name: CONTEXTFIELDVALUE_SCALAR_NAME,
})]
}
fn minimum_federation_version(&self) -> &Version {
&self.minimum_federation_version
}
fn purpose(&self) -> Option<Purpose> {
Some(Purpose::SECURITY)
}
}
pub(crate) static CONTEXT_VERSIONS: LazyLock<SpecDefinitions<ContextSpecDefinition>> =
LazyLock::new(|| {
let mut definitions = SpecDefinitions::new(Identity::context_identity());
definitions.add(ContextSpecDefinition::new(
Version { major: 0, minor: 1 },
Version { major: 2, minor: 8 },
));
definitions
});