pub(crate) mod input;
pub(crate) mod selection;
use std::collections::VecDeque;
use apollo_compiler::Name;
use apollo_compiler::ast::Directive;
use indexmap::IndexSet;
use crate::schema::FederationSchema;
use crate::schema::ValidFederationSchema;
pub(crate) fn filter_directives<'a, D, I, O>(deny_list: &IndexSet<Name>, directives: D) -> O
where
D: IntoIterator<Item = &'a I>,
I: 'a + AsRef<Directive> + Clone,
O: FromIterator<I>,
{
directives
.into_iter()
.filter(|d| !deny_list.contains(&d.as_ref().name))
.cloned()
.collect()
}
macro_rules! try_pre_insert {
($schema:expr, $pos:expr) => {{
if let Some(old_pos) = $schema.try_get_type($pos.type_name.clone()) {
let pos = $crate::schema::position::TypeDefinitionPosition::from($pos.clone());
if old_pos != pos {
Err($crate::FederationError::internal(format!(
"found different type when upserting: expected {:?} found {:?}",
pos, old_pos
)))
} else {
Ok(())
}
} else if $schema.referencers().contains_type_name(&$pos.type_name) {
Ok(())
} else {
$pos.pre_insert($schema)
}
}};
}
macro_rules! try_insert {
($schema:expr, $pos:expr, $def:expr) => {{
if let Some(old_pos) = $schema.try_get_type($pos.type_name.clone()) {
let pos = $crate::schema::position::TypeDefinitionPosition::from($pos.clone());
if old_pos != pos {
Err($crate::FederationError::internal(format!(
"found different type when upserting: expected {:?} found {:?}",
pos, old_pos
)))
} else {
Ok(())
}
} else {
$pos.insert($schema, $def)
}
}};
}
pub(crate) use try_insert;
pub(crate) use try_pre_insert;
pub(crate) trait FieldVisitor<Field>: Sized {
type Error;
fn visit(&mut self, field: Field) -> Result<(), Self::Error>;
}
pub(crate) trait GroupVisitor<Group, Field>
where
Self: FieldVisitor<Field>,
Field: Clone,
{
fn try_get_group_for_field(
&self,
field: &Field,
) -> Result<Option<Group>, <Self as FieldVisitor<Field>>::Error>;
fn enter_group(
&mut self,
group: &Group,
) -> Result<Vec<Field>, <Self as FieldVisitor<Field>>::Error>;
fn exit_group(&mut self) -> Result<(), <Self as FieldVisitor<Field>>::Error>;
fn walk(mut self, entry: Group) -> Result<Self, <Self as FieldVisitor<Field>>::Error> {
let mut to_visit =
VecDeque::from_iter(self.enter_group(&entry)?.into_iter().map(|n| (0i32, n)));
let mut current_depth = 0;
while let Some((depth, next)) = to_visit.pop_front() {
for _ in depth..current_depth {
self.exit_group()?;
}
current_depth = depth;
self.visit(next.clone())?;
if let Some(group) = self.try_get_group_for_field(&next)? {
current_depth += 1;
let fields = self.enter_group(&group)?;
fields
.into_iter()
.rev()
.for_each(|s| to_visit.push_front((current_depth, s)));
}
}
for _ in 0..=current_depth {
self.exit_group()?;
}
Ok(self)
}
}
pub(crate) struct SchemaVisitor<'a, Group, GroupType> {
directive_deny_list: &'a IndexSet<Name>,
original_schema: &'a ValidFederationSchema,
to_schema: &'a mut FederationSchema,
type_stack: Vec<(Group, GroupType)>,
}
impl<'a, Group, GroupType> SchemaVisitor<'a, Group, GroupType> {
pub(crate) fn new(
original_schema: &'a ValidFederationSchema,
to_schema: &'a mut FederationSchema,
directive_deny_list: &'a IndexSet<Name>,
) -> Self {
SchemaVisitor {
directive_deny_list,
original_schema,
to_schema,
type_stack: Vec::new(),
}
}
}
#[cfg(test)]
mod tests {
use insta::assert_snapshot;
use itertools::Itertools;
use crate::connectors::JSONSelection;
use crate::connectors::SubSelection;
use crate::connectors::expand::visitors::FieldVisitor;
use crate::connectors::expand::visitors::GroupVisitor;
use crate::connectors::json_selection::NamedSelection;
use crate::error::FederationError;
struct TestVisitor<'a> {
depth_stack: Vec<usize>,
visited: &'a mut Vec<(usize, String)>,
}
impl<'a> TestVisitor<'a> {
fn new(visited: &'a mut Vec<(usize, String)>) -> Self {
Self {
depth_stack: Vec::new(),
visited,
}
}
fn last_depth(&self) -> Option<usize> {
self.depth_stack.last().copied()
}
}
fn print_visited(visited: Vec<(usize, String)>) -> String {
let mut result = String::new();
for (depth, visited) in visited {
result.push_str(&format!("{}{visited}\n", "| ".repeat(depth)));
}
result
}
impl FieldVisitor<NamedSelection> for TestVisitor<'_> {
type Error = FederationError;
fn visit<'a>(&mut self, field: NamedSelection) -> Result<(), Self::Error> {
for name in field.names() {
self.visited
.push((self.last_depth().unwrap_or_default(), name.to_string()));
}
Ok(())
}
}
impl GroupVisitor<SubSelection, NamedSelection> for TestVisitor<'_> {
fn try_get_group_for_field(
&self,
field: &NamedSelection,
) -> Result<Option<SubSelection>, FederationError> {
Ok(field.next_subselection().cloned())
}
fn enter_group(
&mut self,
group: &SubSelection,
) -> Result<Vec<NamedSelection>, FederationError> {
let next_depth = self.last_depth().map(|d| d + 1).unwrap_or(0);
self.depth_stack.push(next_depth);
Ok(group
.selections_iter()
.sorted_by_key(|s| s.names())
.cloned()
.collect())
}
fn exit_group(&mut self) -> Result<(), FederationError> {
self.depth_stack.pop().unwrap();
Ok(())
}
}
#[test]
fn it_iterates_over_empty_path() {
let mut visited = Vec::new();
let visitor = TestVisitor::new(&mut visited);
let selection = JSONSelection::parse("").unwrap();
visitor
.walk(selection.next_subselection().cloned().unwrap())
.unwrap();
assert_snapshot!(print_visited(visited), @"");
}
#[test]
fn it_iterates_over_simple_selection() {
let mut visited = Vec::new();
let visitor = TestVisitor::new(&mut visited);
let selection = JSONSelection::parse("a b c d").unwrap();
visitor
.walk(selection.next_subselection().cloned().unwrap())
.unwrap();
assert_snapshot!(print_visited(visited), @r###"
a
b
c
d
"###);
}
#[test]
fn it_iterates_over_aliased_selection() {
let mut visited = Vec::new();
let visitor = TestVisitor::new(&mut visited);
let selection = JSONSelection::parse("a: one b: two c: three d: four").unwrap();
visitor
.walk(selection.next_subselection().cloned().unwrap())
.unwrap();
assert_snapshot!(print_visited(visited), @r###"
a
b
c
d
"###);
}
#[test]
fn it_iterates_over_nested_selection() {
let mut visited = Vec::new();
let visitor = TestVisitor::new(&mut visited);
let selection = JSONSelection::parse("a { b { c { d { e } } } } f").unwrap();
visitor
.walk(selection.next_subselection().cloned().unwrap())
.unwrap();
assert_snapshot!(print_visited(visited), @r###"
a
| b
| | c
| | | d
| | | | e
f
"###);
}
#[test]
fn it_iterates_over_paths() {
let mut visited = Vec::new();
let visitor = TestVisitor::new(&mut visited);
let selection = JSONSelection::parse(
"a
$.b {
c
$.d {
e
f: g.h { i }
}
}
j",
)
.unwrap();
visitor
.walk(selection.next_subselection().cloned().unwrap())
.unwrap();
assert_snapshot!(print_visited(visited), @r###"
a
c
e
f
| i
j
"###);
}
#[test]
fn it_iterates_over_complex_selection() {
let mut visited = Vec::new();
let visitor = TestVisitor::new(&mut visited);
let selection = JSONSelection::parse(
"id
name
username
email
address {
street
suite
city
zipcode
geo {
lat
lng
}
}
phone
website
company {
name
catchPhrase
bs
}",
)
.unwrap();
visitor
.walk(selection.next_subselection().cloned().unwrap())
.unwrap();
assert_snapshot!(print_visited(visited), @r###"
address
| city
| geo
| | lat
| | lng
| street
| suite
| zipcode
company
| bs
| catchPhrase
| name
email
id
name
phone
username
website
"###);
}
}