use apollo_compiler::ExecutableDocument;
use apollo_compiler::ast;
use apollo_compiler::executable;
use apollo_compiler::schema::FieldLookupError;
use tower::BoxError;
pub(crate) fn document(
visitor: &mut impl Visitor,
document: &ExecutableDocument,
operation_name: Option<&str>,
) -> Result<(), BoxError> {
if let Ok(operation) = document.operations.get(operation_name) {
visitor.operation(operation.object_type().as_str(), operation)?;
}
for fragment in document.fragments.values() {
visitor.fragment(fragment)?;
}
Ok(())
}
pub(crate) trait Visitor: Sized {
fn schema(&self) -> &apollo_compiler::Schema;
fn operation(&mut self, root_type: &str, def: &executable::Operation) -> Result<(), BoxError> {
operation(self, root_type, def)
}
fn fragment(&mut self, def: &executable::Fragment) -> Result<(), BoxError> {
fragment(self, def)
}
fn field(
&mut self,
_parent_type: &str,
field_def: &ast::FieldDefinition,
def: &executable::Field,
) -> Result<(), BoxError> {
field(self, field_def, def)
}
fn fragment_spread(&mut self, def: &executable::FragmentSpread) -> Result<(), BoxError> {
fragment_spread(self, def)
}
fn inline_fragment(
&mut self,
parent_type: &str,
def: &executable::InlineFragment,
) -> Result<(), BoxError> {
inline_fragment(self, parent_type, def)
}
}
pub(crate) fn operation(
visitor: &mut impl Visitor,
root_type: &str,
def: &executable::Operation,
) -> Result<(), BoxError> {
selection_set(visitor, root_type, &def.selection_set.selections)
}
pub(crate) fn fragment(
visitor: &mut impl Visitor,
def: &executable::Fragment,
) -> Result<(), BoxError> {
selection_set(visitor, def.type_condition(), &def.selection_set.selections)?;
Ok(())
}
pub(crate) fn field(
visitor: &mut impl Visitor,
field_def: &ast::FieldDefinition,
def: &executable::Field,
) -> Result<(), BoxError> {
selection_set(
visitor,
field_def.ty.inner_named_type(),
&def.selection_set.selections,
)
}
pub(crate) fn fragment_spread(
visitor: &mut impl Visitor,
def: &executable::FragmentSpread,
) -> Result<(), BoxError> {
let _ = (visitor, def); Ok(())
}
pub(crate) fn inline_fragment(
visitor: &mut impl Visitor,
parent_type: &str,
def: &executable::InlineFragment,
) -> Result<(), BoxError> {
selection_set(visitor, parent_type, &def.selection_set.selections)
}
pub(crate) fn selection_set(
visitor: &mut impl Visitor,
parent_type: &str,
set: &[executable::Selection],
) -> Result<(), BoxError> {
set.iter().try_for_each(|def| match def {
executable::Selection::Field(def) => {
let field_def = &visitor
.schema()
.type_field(parent_type, &def.name)
.map_err(|e| match e {
FieldLookupError::NoSuchType => format!("type `{parent_type}` not defined"),
FieldLookupError::NoSuchField(_, _) => {
format!("no field `{}` in type `{parent_type}`", &def.name)
}
})?
.clone();
visitor.field(parent_type, field_def, def)
}
executable::Selection::FragmentSpread(def) => visitor.fragment_spread(def),
executable::Selection::InlineFragment(def) => {
let fragment_type = def
.type_condition
.as_ref()
.map(|s| s.as_str())
.unwrap_or(parent_type);
visitor.inline_fragment(fragment_type, def)
}
})
}
#[test]
fn test_count_fields() {
use apollo_compiler::validation::Valid;
struct CountFields {
schema: apollo_compiler::Schema,
fields: u32,
}
impl Visitor for CountFields {
fn field(
&mut self,
_parent_type: &str,
field_def: &ast::FieldDefinition,
def: &executable::Field,
) -> Result<(), BoxError> {
self.fields += 1;
field(self, field_def, def)
}
fn schema(&self) -> &apollo_compiler::Schema {
&self.schema
}
}
let schema = "
type Query {
a(id: ID): String
b: Int
next: Query
}
directive @defer(label: String, if: Boolean! = true) on FRAGMENT_SPREAD | INLINE_FRAGMENT";
let query = "
query($id: ID = null) {
a(id: $id)
... @defer {
b
}
... F
... F
}
fragment F on Query {
next {
a
}
}
";
let ast = apollo_compiler::ast::Document::parse(schema, "").unwrap();
let schema = ast.to_schema_validate().unwrap();
let schema = schema.into_inner();
let executable =
ExecutableDocument::parse(Valid::assume_valid_ref(&schema), query, "").unwrap();
let mut visitor = CountFields { fields: 0, schema };
document(&mut visitor, &executable, None).unwrap();
assert_eq!(visitor.fields, 4)
}