use super::*;
use crate::ty;
use std::sync::Arc;
pub(crate) struct BuildErrors<'a> {
pub(crate) errors: &'a mut DiagnosticList,
pub(crate) path: SelectionPath,
}
pub struct ExecutableDocumentBuilder<'schema, 'errors> {
pub(crate) document: ExecutableDocument,
schema: Option<&'schema Schema>,
pub(crate) errors: &'errors mut DiagnosticList,
multiple_anonymous: bool,
}
impl<'schema, 'errors> ExecutableDocumentBuilder<'schema, 'errors> {
pub fn new(schema: Option<&'schema Schema>, errors: &'errors mut DiagnosticList) -> Self {
Self {
document: ExecutableDocument::new(),
schema,
errors,
multiple_anonymous: false,
}
}
pub fn parse(
mut self,
source_text: impl Into<String>,
path: impl AsRef<std::path::Path>,
) -> Self {
Parser::new().parse_into_executable_builder(source_text, path, &mut self);
self
}
pub fn add_ast_document(
&mut self,
document: &ast::Document,
type_system_definitions_are_errors: bool,
) {
Arc::make_mut(&mut self.errors.sources)
.extend(document.sources.iter().map(|(k, v)| (*k, v.clone())));
self.add_ast_document_not_adding_sources(document, type_system_definitions_are_errors);
}
pub(crate) fn add_ast_document_not_adding_sources(
&mut self,
document: &ast::Document,
type_system_definitions_are_errors: bool,
) {
let mut errors = BuildErrors {
errors: self.errors,
path: SelectionPath {
nested_fields: Vec::new(),
root: ExecutableDefinitionName::AnonymousOperation(ast::OperationType::Query),
},
};
for definition in &document.definitions {
debug_assert!(errors.path.nested_fields.is_empty());
match definition {
ast::Definition::OperationDefinition(operation) => {
if let Some(name) = &operation.name {
if let Some(anonymous) = &self.document.operations.anonymous {
errors.errors.push(
anonymous.location(),
BuildError::AmbiguousAnonymousOperation,
)
}
if let Entry::Vacant(entry) =
self.document.operations.named.entry(name.clone())
{
errors.path.root = ExecutableDefinitionName::NamedOperation(
operation.operation_type,
name.clone(),
);
if let Some(op) =
Operation::from_ast(self.schema, &mut errors, operation)
{
entry.insert(operation.same_location(op));
} else {
errors.errors.push(
operation.location(),
BuildError::UndefinedRootOperation {
operation_type: operation.operation_type.name(),
},
)
}
} else {
let (key, _) =
self.document.operations.named.get_key_value(name).unwrap();
errors.errors.push(
name.location(),
BuildError::OperationNameCollision {
name_at_previous_location: key.clone(),
},
);
}
} else if let Some(previous) = &self.document.operations.anonymous {
if !self.multiple_anonymous {
self.multiple_anonymous = true;
errors
.errors
.push(previous.location(), BuildError::AmbiguousAnonymousOperation)
}
errors.errors.push(
operation.location(),
BuildError::AmbiguousAnonymousOperation,
)
} else if !self.document.operations.named.is_empty() {
errors.errors.push(
operation.location(),
BuildError::AmbiguousAnonymousOperation,
)
} else {
errors.path.root =
ExecutableDefinitionName::AnonymousOperation(operation.operation_type);
if let Some(op) = Operation::from_ast(self.schema, &mut errors, operation) {
self.document.operations.anonymous = Some(operation.same_location(op));
} else {
errors.errors.push(
operation.location(),
BuildError::UndefinedRootOperation {
operation_type: operation.operation_type.name(),
},
)
}
}
}
ast::Definition::FragmentDefinition(fragment) => {
if let Entry::Vacant(entry) =
self.document.fragments.entry(fragment.name.clone())
{
errors.path.root =
ExecutableDefinitionName::Fragment(fragment.name.clone());
if let Some(node) = Fragment::from_ast(self.schema, &mut errors, fragment) {
entry.insert(fragment.same_location(node));
}
} else {
let (key, _) = self
.document
.fragments
.get_key_value(&fragment.name)
.unwrap();
errors.errors.push(
fragment.name.location(),
BuildError::FragmentNameCollision {
name_at_previous_location: key.clone(),
},
)
}
}
_ => {
if type_system_definitions_are_errors {
errors.errors.push(
definition.location(),
BuildError::TypeSystemDefinition {
name: definition.name().cloned(),
describe: definition.describe(),
},
)
}
}
}
}
Arc::make_mut(&mut self.document.sources)
.extend(document.sources.iter().map(|(k, v)| (*k, v.clone())));
}
pub fn build(self) -> ExecutableDocument {
self.build_inner()
}
pub(crate) fn build_inner(mut self) -> ExecutableDocument {
self.document.sources = self.errors.sources.clone();
self.document
}
}
pub(crate) fn document_from_ast(
schema: Option<&Schema>,
document: &ast::Document,
errors: &mut DiagnosticList,
type_system_definitions_are_errors: bool,
) -> ExecutableDocument {
let mut builder = ExecutableDocumentBuilder::new(schema, errors);
builder.add_ast_document_not_adding_sources(document, type_system_definitions_are_errors);
let doc = builder.build_inner();
ExecutableDocument {
sources: document.sources.clone(),
operations: doc.operations,
fragments: doc.fragments,
}
}
impl Operation {
fn from_ast(
schema: Option<&Schema>,
errors: &mut BuildErrors,
ast: &ast::OperationDefinition,
) -> Option<Self> {
let ty = if let Some(s) = schema {
s.root_operation(ast.operation_type)?.clone()
} else {
ast.operation_type.default_type_name().clone()
};
let mut selection_set = SelectionSet::new(ty);
selection_set.extend_from_ast(schema, errors, &ast.selection_set);
Some(Self {
operation_type: ast.operation_type,
name: ast.name.clone(),
variables: ast.variables.clone(),
directives: ast.directives.clone(),
selection_set,
})
}
}
impl Fragment {
fn from_ast(
schema: Option<&Schema>,
errors: &mut BuildErrors,
ast: &ast::FragmentDefinition,
) -> Option<Self> {
if let Some(schema) = schema {
if !schema.types.contains_key(&ast.type_condition) {
errors.errors.push(
ast.type_condition.location(),
BuildError::UndefinedTypeInNamedFragmentTypeCondition {
type_name: ast.type_condition.clone(),
fragment_name: ast.name.clone(),
},
);
return None;
}
}
let mut selection_set = SelectionSet::new(ast.type_condition.clone());
selection_set.extend_from_ast(schema, errors, &ast.selection_set);
Some(Self {
name: ast.name.clone(),
directives: ast.directives.clone(),
selection_set,
})
}
}
impl SelectionSet {
pub(crate) fn extend_from_ast(
&mut self,
schema: Option<&Schema>,
errors: &mut BuildErrors,
ast_selections: &[ast::Selection],
) {
for selection in ast_selections {
match selection {
ast::Selection::Field(ast) => {
let field_def_result = if let Some(s) = schema {
s.type_field(&self.ty, &ast.name).map(|c| c.node.clone())
} else {
Ok(Node::new(ast::FieldDefinition {
description: None,
name: ast.name.clone(),
arguments: Vec::new(),
ty: ty!(UNKNOWN),
directives: Default::default(),
}))
};
errors
.path
.nested_fields
.push(ast.alias.clone().unwrap_or_else(|| ast.name.clone()));
match field_def_result {
Ok(field_def) => {
let leaf = ast.selection_set.is_empty();
let type_name = field_def.ty.inner_named_type();
match schema
.as_ref()
.and_then(|schema| schema.types.get(type_name))
{
Some(schema::ExtendedType::Scalar(_)) if !leaf => {
errors.errors.push(
ast.location(),
BuildError::SubselectionOnScalarType {
type_name: type_name.clone(),
path: errors.path.clone(),
},
)
}
Some(schema::ExtendedType::Enum(_)) if !leaf => errors.errors.push(
ast.location(),
BuildError::SubselectionOnEnumType {
type_name: type_name.clone(),
path: errors.path.clone(),
},
),
_ => self.push(
ast.same_location(
Field::new(ast.name.clone(), field_def)
.with_opt_alias(ast.alias.clone())
.with_arguments(ast.arguments.iter().cloned())
.with_directives(ast.directives.iter().cloned())
.with_ast_selections(
schema,
errors,
&ast.selection_set,
),
),
),
}
}
Err(schema::FieldLookupError::NoSuchField(type_name, _)) => {
errors.errors.push(
ast.name.location(),
BuildError::UndefinedField {
type_name: type_name.clone(),
field_name: ast.name.clone(),
path: errors.path.clone(),
},
)
}
Err(schema::FieldLookupError::NoSuchType) => {
}
}
errors.path.nested_fields.pop();
}
ast::Selection::FragmentSpread(ast) => self.push(
ast.same_location(
self.new_fragment_spread(ast.fragment_name.clone())
.with_directives(ast.directives.iter().cloned()),
),
),
ast::Selection::InlineFragment(ast) => {
let opt_type_condition = ast.type_condition.clone();
match (&opt_type_condition, schema) {
(Some(type_condition), Some(schema))
if !schema.types.contains_key(type_condition) =>
{
errors.errors.push(
type_condition.location(),
BuildError::UndefinedTypeInInlineFragmentTypeCondition {
type_name: type_condition.clone(),
path: errors.path.clone(),
},
)
}
_ => self.push(
ast.same_location(
self.new_inline_fragment(opt_type_condition)
.with_directives(ast.directives.iter().cloned())
.with_ast_selections(schema, errors, &ast.selection_set),
),
),
}
}
}
}
}
}
impl Field {
fn with_ast_selections(
mut self,
schema: Option<&Schema>,
errors: &mut BuildErrors,
ast_selections: &[ast::Selection],
) -> Self {
self.selection_set
.extend_from_ast(schema, errors, ast_selections);
self
}
}
impl InlineFragment {
fn with_ast_selections(
mut self,
schema: Option<&Schema>,
errors: &mut BuildErrors,
ast_selections: &[ast::Selection],
) -> Self {
self.selection_set
.extend_from_ast(schema, errors, ast_selections);
self
}
}