use std::fmt;
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::Value;
use apollo_compiler::parser::LineColumn;
use apollo_compiler::schema::Component;
use crate::error::FederationError;
use crate::link::link_spec_definition::CORE_VERSIONS;
use crate::link::link_spec_definition::LINK_VERSIONS;
use crate::link::spec::Identity;
use crate::link::spec::Url;
pub(crate) mod argument;
pub(crate) mod authenticated_spec_definition;
pub(crate) mod cache_tag_spec_definition;
pub(crate) mod context_spec_definition;
pub mod cost_spec_definition;
pub(crate) mod federation_spec_definition;
pub(crate) mod graphql_definition;
pub(crate) mod inaccessible_spec_definition;
pub(crate) mod join_spec_definition;
pub(crate) mod link_spec_definition;
pub mod metadata;
pub(crate) mod policy_spec_definition;
pub(crate) mod requires_scopes_spec_definition;
pub mod spec;
pub(crate) mod spec_definition;
pub(crate) mod spec_registry;
pub(crate) mod tag_spec_definition;
#[derive(Clone, Copy, Eq, PartialEq, Debug)]
pub enum Purpose {
SECURITY,
EXECUTION,
}
impl fmt::Display for Purpose {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
Value::from(self).fmt(f)
}
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct Import {
pub element: Name,
pub is_directive: bool,
pub alias: Option<Name>,
}
impl Import {
pub fn name_in_schema(&self) -> &Name {
self.alias.as_ref().unwrap_or(&self.element)
}
pub fn element_name_in_spec(&self) -> ElementName {
ElementName {
name: self.element.clone(),
is_directive: self.is_directive,
}
}
pub fn element_name_in_schema(&self) -> ElementName {
ElementName {
name: self.name_in_schema().clone(),
is_directive: self.is_directive,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct ElementName {
pub name: Name,
pub is_directive: bool,
}
impl fmt::Display for ElementName {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.is_directive {
f.write_str("@")?;
}
f.write_str(&self.name)
}
}
impl fmt::Display for Import {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
Value::from(self).fmt(f)
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Link {
pub url: Url,
pub spec_alias: Option<Name>,
pub imports: Vec<Arc<Import>>,
pub purpose: Option<Purpose>,
pub line_column_range: Option<Range<LineColumn>>,
}
impl Link {
pub(crate) fn from_directive_application_when_link_spec_unknown(
directive: &Node<Directive>,
schema: &Schema,
) -> Result<Link, FederationError> {
LINK_VERSIONS
.latest()
.link_from_directive(directive, schema)
.or_else(|error| {
CORE_VERSIONS
.latest()
.link_from_directive(directive, schema)
.or(Err(error))
})
}
pub fn spec_name_in_schema(&self) -> Name {
if let Some(spec_alias) = &self.spec_alias {
return spec_alias.clone();
}
let name = &self.url.identity.name;
name.clone().try_into().unwrap_or_else(|_| {
Name::new_unchecked(name)
})
}
pub fn directive_name_in_schema(&self, name: &Name) -> Name {
if let Some(import) = self.imports.iter().find(|i| i.element == *name) {
import.alias.clone().unwrap_or_else(|| name.clone())
} else if name.as_str() == self.url.identity.name.as_ref() {
self.spec_name_in_schema().clone()
} else {
Name::new_unchecked(&format!("{}__{}", self.spec_name_in_schema(), name))
}
}
pub(crate) fn directive_name_in_schema_for_core_arguments(
spec_url: &Url,
spec_name_in_schema: &Name,
imports: &[Import],
directive_name_in_spec: &Name,
) -> Name {
if let Some(element_import) = imports
.iter()
.find(|i| i.element == *directive_name_in_spec)
{
element_import.name_in_schema().clone()
} else if spec_url.identity.name.as_ref() == directive_name_in_spec.as_str() {
spec_name_in_schema.clone()
} else {
Name::new_unchecked(format!("{spec_name_in_schema}__{directive_name_in_spec}").as_str())
}
}
pub fn type_name_in_schema(&self, name: &Name) -> Name {
if let Some(import) = self.imports.iter().find(|i| i.element == *name) {
import.alias.clone().unwrap_or_else(|| name.clone())
} else {
Name::new_unchecked(&format!("{}__{}", self.spec_name_in_schema(), name))
}
}
pub(crate) fn for_identity<'schema>(
schema: &'schema Schema,
identity: &Identity,
) -> Option<(Self, &'schema Component<Directive>)> {
schema
.schema_definition
.directives
.iter()
.find_map(|directive| {
let link =
Link::from_directive_application_when_link_spec_unknown(directive, schema)
.ok()?;
if link.url.identity == *identity {
Some((link, directive))
} else {
None
}
})
}
}
impl fmt::Display for Link {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
LINK_VERSIONS.latest().directive_from_link(self).fmt(f)
}
}