use std::ops::Deref;
use apollo_compiler::Node;
use apollo_compiler::ast::InputValueDefinition;
use apollo_compiler::schema::Component;
use apollo_compiler::schema::InputObjectType;
use indexmap::IndexMap;
use super::FieldVisitor;
use super::GroupVisitor;
use super::SchemaVisitor;
use super::filter_directives;
use super::try_insert;
use super::try_pre_insert;
use crate::error::FederationError;
use crate::schema::position::InputObjectFieldDefinitionPosition;
use crate::schema::position::InputObjectTypeDefinitionPosition;
use crate::schema::position::TypeDefinitionPosition;
impl FieldVisitor<InputObjectFieldDefinitionPosition>
for SchemaVisitor<'_, InputObjectTypeDefinitionPosition, InputObjectType>
{
type Error = FederationError;
fn visit<'a>(&mut self, field: InputObjectFieldDefinitionPosition) -> Result<(), Self::Error> {
let (_, r#type) = self.type_stack.last_mut().ok_or_else(|| {
FederationError::internal("tried to visit a field in a group not yet visited")
})?;
let field_def = field.get(self.original_schema.schema())?;
let new_field = InputValueDefinition {
description: field_def.description.clone(),
name: field_def.name.clone(),
default_value: field_def.default_value.clone(),
ty: field_def.ty.clone(),
directives: filter_directives(self.directive_deny_list, &field_def.directives),
};
let input_type = self
.original_schema
.get_type(field_def.ty.inner_named_type().clone())?;
match input_type {
TypeDefinitionPosition::Scalar(pos) => {
try_pre_insert!(self.to_schema, pos)?;
try_insert!(
self.to_schema,
pos,
pos.get(self.original_schema.schema())?.clone()
)?;
}
TypeDefinitionPosition::Enum(pos) => {
try_pre_insert!(self.to_schema, pos)?;
try_insert!(
self.to_schema,
pos,
pos.get(self.original_schema.schema())?.clone()
)?;
}
_ => {}
}
if let Some(old_field) = r#type.fields.get(&field.field_name) {
if *old_field.deref().deref() != new_field {
return Err(FederationError::internal(format!(
"tried to write field to existing type, but field type was different. expected {new_field:?} found {old_field:?}"
)));
}
} else {
r#type
.fields
.insert(field.field_name, Component::new(new_field));
}
Ok(())
}
}
impl GroupVisitor<InputObjectTypeDefinitionPosition, InputObjectFieldDefinitionPosition>
for SchemaVisitor<'_, InputObjectTypeDefinitionPosition, InputObjectType>
{
fn try_get_group_for_field(
&self,
field: &InputObjectFieldDefinitionPosition,
) -> Result<Option<InputObjectTypeDefinitionPosition>, FederationError> {
let field_type = field.get(self.original_schema.schema())?;
let inner_type = self
.original_schema
.get_type(field_type.ty.inner_named_type().clone())?;
match inner_type {
TypeDefinitionPosition::InputObject(input) => Ok(Some(input)),
TypeDefinitionPosition::Scalar(_) | TypeDefinitionPosition::Enum(_) => Ok(None),
other => Err(FederationError::internal(format!(
"input objects cannot include fields of type: {}",
other.type_name()
))),
}
}
fn enter_group<'a>(
&mut self,
group: &InputObjectTypeDefinitionPosition,
) -> Result<Vec<InputObjectFieldDefinitionPosition>, FederationError> {
try_pre_insert!(self.to_schema, group)?;
let group_def = group.get(self.original_schema.schema())?;
let output_type = InputObjectType {
description: group_def.description.clone(),
name: group_def.name.clone(),
directives: filter_directives(self.directive_deny_list, &group_def.directives),
fields: IndexMap::with_hasher(Default::default()), };
self.type_stack.push((group.clone(), output_type));
let def = group.get(self.original_schema.schema())?;
Ok(def.fields.keys().cloned().map(|f| group.field(f)).collect())
}
fn exit_group(&mut self) -> Result<(), FederationError> {
let (definition, r#type) = self
.type_stack
.pop()
.ok_or_else(|| FederationError::internal("tried to exit a group not yet visited"))?;
try_insert!(self.to_schema, definition, Node::new(r#type))
}
}