use crate::ast;
use crate::collections::IndexMap;
use crate::coordinate::FieldArgumentCoordinate;
use crate::coordinate::TypeAttributeCoordinate;
use crate::parser::Parser;
use crate::parser::SourceMap;
use crate::parser::SourceSpan;
use crate::schema;
use crate::validation::DiagnosticList;
use crate::validation::Valid;
use crate::validation::WithErrors;
use crate::Node;
use crate::Schema;
use indexmap::map::Entry;
use std::fmt;
use std::path::Path;
use std::sync::Arc;
pub(crate) mod from_ast;
mod serialize;
pub(crate) mod validation;
pub use crate::ast::Argument;
pub use crate::ast::Directive;
pub use crate::ast::DirectiveList;
pub use crate::ast::NamedType;
pub use crate::ast::OperationType;
pub use crate::ast::Type;
pub use crate::ast::Value;
pub use crate::ast::VariableDefinition;
use crate::schema::ArgumentByNameError;
pub use crate::Name;
#[derive(Debug, Clone, Default)]
pub struct ExecutableDocument {
pub sources: SourceMap,
pub operations: OperationMap,
pub fragments: FragmentMap,
}
#[derive(Debug, Clone, Default, PartialEq)]
pub struct OperationMap {
pub anonymous: Option<Node<Operation>>,
pub named: IndexMap<Name, Node<Operation>>,
}
pub type FragmentMap = IndexMap<Name, Node<Fragment>>;
#[derive(Debug, Clone)]
pub struct FieldSet {
pub sources: SourceMap,
pub selection_set: SelectionSet,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Operation {
pub operation_type: OperationType,
pub name: Option<Name>,
pub variables: Vec<Node<VariableDefinition>>,
pub directives: DirectiveList,
pub selection_set: SelectionSet,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Fragment {
pub name: Name,
pub directives: DirectiveList,
pub selection_set: SelectionSet,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct SelectionSet {
pub ty: NamedType,
pub selections: Vec<Selection>,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum Selection {
Field(Node<Field>),
FragmentSpread(Node<FragmentSpread>),
InlineFragment(Node<InlineFragment>),
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Field {
pub definition: Node<schema::FieldDefinition>,
pub alias: Option<Name>,
pub name: Name,
pub arguments: Vec<Node<Argument>>,
pub directives: DirectiveList,
pub selection_set: SelectionSet,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct FragmentSpread {
pub fragment_name: Name,
pub directives: DirectiveList,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct InlineFragment {
pub type_condition: Option<NamedType>,
pub directives: DirectiveList,
pub selection_set: SelectionSet,
}
#[derive(thiserror::Error, Debug, Clone)]
pub(crate) enum BuildError {
#[error("an executable document must not contain {describe}")]
TypeSystemDefinition {
name: Option<Name>,
describe: &'static str,
},
#[error("anonymous operation cannot be selected when the document contains other operations")]
AmbiguousAnonymousOperation,
#[error(
"the operation `{name_at_previous_location}` is defined multiple times in the document"
)]
OperationNameCollision { name_at_previous_location: Name },
#[error(
"the fragment `{name_at_previous_location}` is defined multiple times in the document"
)]
FragmentNameCollision { name_at_previous_location: Name },
#[error("`{operation_type}` root operation type is not defined")]
UndefinedRootOperation { operation_type: &'static str },
#[error(
"type condition `{type_name}` of fragment `{fragment_name}` \
is not a type defined in the schema"
)]
UndefinedTypeInNamedFragmentTypeCondition {
type_name: NamedType,
fragment_name: Name,
},
#[error("type condition `{type_name}` of inline fragment is not a type defined in the schema")]
UndefinedTypeInInlineFragmentTypeCondition {
type_name: NamedType,
path: SelectionPath,
},
#[error("field selection of scalar type `{type_name}` must not have subselections")]
SubselectionOnScalarType {
type_name: NamedType,
path: SelectionPath,
},
#[error("field selection of enum type `{type_name}` must not have subselections")]
SubselectionOnEnumType {
type_name: NamedType,
path: SelectionPath,
},
#[error("type `{type_name}` does not have a field `{field_name}`")]
UndefinedField {
type_name: NamedType,
field_name: Name,
path: SelectionPath,
},
#[error(
"{} can only have one root field",
subscription_name_or_anonymous(name)
)]
SubscriptionUsesMultipleFields {
name: Option<Name>,
fields: Vec<Name>,
},
#[error(
"{} can not have an introspection field as a root field",
subscription_name_or_anonymous(name)
)]
SubscriptionUsesIntrospection {
name: Option<Name>,
field: Name,
},
#[error("{0}")]
ConflictingFieldType(Box<ConflictingFieldType>),
#[error("{0}")]
ConflictingFieldArgument(Box<ConflictingFieldArgument>),
#[error("{0}")]
ConflictingFieldName(Box<ConflictingFieldName>),
}
#[derive(thiserror::Error, Debug, Clone)]
#[error("operation must not select different types using the same name `{alias}`")]
pub(crate) struct ConflictingFieldType {
pub(crate) alias: Name,
pub(crate) original_location: Option<SourceSpan>,
pub(crate) original_coordinate: TypeAttributeCoordinate,
pub(crate) original_type: Type,
pub(crate) conflicting_location: Option<SourceSpan>,
pub(crate) conflicting_coordinate: TypeAttributeCoordinate,
pub(crate) conflicting_type: Type,
}
#[derive(thiserror::Error, Debug, Clone)]
#[error("operation must not provide conflicting field arguments for the same name `{alias}`")]
pub(crate) struct ConflictingFieldArgument {
pub(crate) alias: Name,
pub(crate) original_location: Option<SourceSpan>,
pub(crate) original_coordinate: FieldArgumentCoordinate,
pub(crate) original_value: Option<Value>,
pub(crate) conflicting_location: Option<SourceSpan>,
pub(crate) conflicting_coordinate: FieldArgumentCoordinate,
pub(crate) conflicting_value: Option<Value>,
}
#[derive(thiserror::Error, Debug, Clone)]
#[error("cannot select different fields into the same alias `{alias}`")]
pub(crate) struct ConflictingFieldName {
pub(crate) alias: Name,
pub(crate) original_location: Option<SourceSpan>,
pub(crate) original_selection: TypeAttributeCoordinate,
pub(crate) conflicting_location: Option<SourceSpan>,
pub(crate) conflicting_selection: TypeAttributeCoordinate,
}
fn subscription_name_or_anonymous(name: &Option<Name>) -> impl std::fmt::Display + '_ {
crate::validation::diagnostics::NameOrAnon {
name: name.as_ref(),
if_some_prefix: "subscription",
if_none: "anonymous subscription",
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct SelectionPath {
pub(crate) root: ExecutableDefinitionName,
pub(crate) nested_fields: Vec<Name>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) enum ExecutableDefinitionName {
AnonymousOperation(ast::OperationType),
NamedOperation(ast::OperationType, Name),
Fragment(Name),
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub struct GetOperationError();
impl ExecutableDocument {
pub fn new() -> Self {
Self::default()
}
pub fn parse(
schema: &Valid<Schema>,
source_text: impl Into<String>,
path: impl AsRef<Path>,
) -> Result<Self, WithErrors<Self>> {
Parser::new().parse_executable(schema, source_text, path)
}
pub fn parse_and_validate(
schema: &Valid<Schema>,
source_text: impl Into<String>,
path: impl AsRef<Path>,
) -> Result<Valid<Self>, WithErrors<Self>> {
let (doc, mut errors) = Parser::new().parse_executable_inner(schema, source_text, path);
Arc::make_mut(&mut errors.sources)
.extend(schema.sources.iter().map(|(k, v)| (*k, v.clone())));
validation::validate_executable_document(&mut errors, schema, &doc);
errors.into_valid_result(doc)
}
pub fn validate(self, schema: &Valid<Schema>) -> Result<Valid<Self>, WithErrors<Self>> {
let mut sources = IndexMap::clone(&schema.sources);
sources.extend(self.sources.iter().map(|(k, v)| (*k, v.clone())));
let mut errors = DiagnosticList::new(Arc::new(sources));
validation::validate_executable_document(&mut errors, schema, &self);
errors.into_valid_result(self)
}
serialize_method!();
}
impl Eq for ExecutableDocument {}
impl PartialEq for ExecutableDocument {
fn eq(&self, other: &Self) -> bool {
let Self {
sources: _,
operations,
fragments,
} = self;
*operations == other.operations && *fragments == other.fragments
}
}
impl OperationMap {
pub fn from_one(operation: impl Into<Node<Operation>>) -> Self {
let mut map = Self::default();
map.insert(operation);
map
}
pub fn is_empty(&self) -> bool {
self.anonymous.is_none() && self.named.is_empty()
}
pub fn len(&self) -> usize {
self.anonymous.is_some() as usize + self.named.len()
}
pub fn iter(&self) -> impl Iterator<Item = &'_ Node<Operation>> {
self.anonymous
.as_ref()
.into_iter()
.chain(self.named.values())
}
pub fn get(&self, name_request: Option<&str>) -> Result<&Node<Operation>, GetOperationError> {
if let Some(name) = name_request {
self.named.get(name)
} else if let Some(op) = &self.anonymous {
self.named.is_empty().then_some(op)
} else {
self.named
.values()
.next()
.and_then(|op| (self.named.len() == 1).then_some(op))
}
.ok_or(GetOperationError())
}
pub fn get_mut(
&mut self,
name_request: Option<&str>,
) -> Result<&mut Operation, GetOperationError> {
if let Some(name) = name_request {
self.named.get_mut(name)
} else if let Some(op) = &mut self.anonymous {
self.named.is_empty().then_some(op)
} else {
let len = self.named.len();
self.named
.values_mut()
.next()
.and_then(|op| (len == 1).then_some(op))
}
.map(Node::make_mut)
.ok_or(GetOperationError())
}
pub fn insert(&mut self, operation: impl Into<Node<Operation>>) -> Option<Node<Operation>> {
let operation = operation.into();
if let Some(name) = &operation.name {
self.named.insert(name.clone(), operation)
} else {
self.anonymous.replace(operation)
}
}
}
impl Operation {
pub fn object_type(&self) -> &NamedType {
&self.selection_set.ty
}
pub fn is_query(&self) -> bool {
self.operation_type == OperationType::Query
}
pub fn is_mutation(&self) -> bool {
self.operation_type == OperationType::Mutation
}
pub fn is_subscription(&self) -> bool {
self.operation_type == OperationType::Subscription
}
pub fn is_introspection(&self, document: &ExecutableDocument) -> bool {
self.is_query()
&& self
.root_fields(document)
.all(|field| matches!(field.name.as_str(), "__type" | "__schema" | "__typename"))
}
pub fn root_fields<'doc>(
&'doc self,
document: &'doc ExecutableDocument,
) -> impl Iterator<Item = &'doc Node<Field>> {
let mut stack = vec![self.selection_set.selections.iter()];
std::iter::from_fn(move || {
while let Some(selection_set_iter) = stack.last_mut() {
match selection_set_iter.next() {
Some(Selection::Field(field)) => {
return Some(field);
}
Some(Selection::InlineFragment(inline)) => {
stack.push(inline.selection_set.selections.iter())
}
Some(Selection::FragmentSpread(spread)) => {
if let Some(def) = document.fragments.get(&spread.fragment_name) {
stack.push(def.selection_set.selections.iter())
} else {
}
}
None => {
stack.pop();
}
}
}
None
})
}
pub fn all_fields<'doc>(
&'doc self,
document: &'doc ExecutableDocument,
) -> impl Iterator<Item = &'doc Node<Field>> {
let mut stack = vec![self.selection_set.selections.iter()];
std::iter::from_fn(move || {
while let Some(selection_set_iter) = stack.last_mut() {
match selection_set_iter.next() {
Some(Selection::Field(field)) => {
if !field.selection_set.is_empty() {
stack.push(field.selection_set.selections.iter())
}
return Some(field);
}
Some(Selection::InlineFragment(inline)) => {
stack.push(inline.selection_set.selections.iter())
}
Some(Selection::FragmentSpread(spread)) => {
if let Some(def) = document.fragments.get(&spread.fragment_name) {
stack.push(def.selection_set.selections.iter())
} else {
}
}
None => {
stack.pop();
}
}
}
None
})
}
serialize_method!();
}
impl Fragment {
pub fn type_condition(&self) -> &NamedType {
&self.selection_set.ty
}
serialize_method!();
}
impl SelectionSet {
pub fn new(ty: NamedType) -> Self {
Self {
ty,
selections: Vec::new(),
}
}
pub fn is_empty(&self) -> bool {
self.selections.is_empty()
}
pub fn push(&mut self, selection: impl Into<Selection>) {
self.selections.push(selection.into())
}
pub fn extend(&mut self, selections: impl IntoIterator<Item = impl Into<Selection>>) {
self.selections
.extend(selections.into_iter().map(|sel| sel.into()))
}
pub fn new_field<'schema>(
&self,
schema: &'schema Schema,
name: Name,
) -> Result<Field, schema::FieldLookupError<'schema>> {
let definition = schema.type_field(&self.ty, &name)?.node.clone();
Ok(Field::new(name, definition))
}
pub fn new_inline_fragment(&self, opt_type_condition: Option<NamedType>) -> InlineFragment {
if let Some(type_condition) = opt_type_condition {
InlineFragment::with_type_condition(type_condition)
} else {
InlineFragment::without_type_condition(self.ty.clone())
}
}
pub fn new_fragment_spread(&self, fragment_name: Name) -> FragmentSpread {
FragmentSpread::new(fragment_name)
}
pub fn fields(&self) -> impl Iterator<Item = &Node<Field>> {
self.selections.iter().filter_map(|sel| sel.as_field())
}
serialize_method!();
}
impl Selection {
pub fn directives(&self) -> &DirectiveList {
match self {
Self::Field(sel) => &sel.directives,
Self::FragmentSpread(sel) => &sel.directives,
Self::InlineFragment(sel) => &sel.directives,
}
}
pub fn as_field(&self) -> Option<&Node<Field>> {
if let Self::Field(field) = self {
Some(field)
} else {
None
}
}
pub fn as_inline_fragment(&self) -> Option<&Node<InlineFragment>> {
if let Self::InlineFragment(inline) = self {
Some(inline)
} else {
None
}
}
pub fn as_fragment_spread(&self) -> Option<&Node<FragmentSpread>> {
if let Self::FragmentSpread(spread) = self {
Some(spread)
} else {
None
}
}
serialize_method!();
}
impl From<Node<Field>> for Selection {
fn from(node: Node<Field>) -> Self {
Self::Field(node)
}
}
impl From<Node<InlineFragment>> for Selection {
fn from(node: Node<InlineFragment>) -> Self {
Self::InlineFragment(node)
}
}
impl From<Node<FragmentSpread>> for Selection {
fn from(node: Node<FragmentSpread>) -> Self {
Self::FragmentSpread(node)
}
}
impl From<Field> for Selection {
fn from(value: Field) -> Self {
Self::Field(Node::new(value))
}
}
impl From<InlineFragment> for Selection {
fn from(value: InlineFragment) -> Self {
Self::InlineFragment(Node::new(value))
}
}
impl From<FragmentSpread> for Selection {
fn from(value: FragmentSpread) -> Self {
Self::FragmentSpread(Node::new(value))
}
}
impl Field {
pub fn new(name: Name, definition: Node<schema::FieldDefinition>) -> Self {
let selection_set = SelectionSet::new(definition.ty.inner_named_type().clone());
Field {
definition,
alias: None,
name,
arguments: Vec::new(),
directives: DirectiveList::new(),
selection_set,
}
}
pub fn with_alias(mut self, alias: Name) -> Self {
self.alias = Some(alias);
self
}
pub fn with_opt_alias(mut self, alias: Option<Name>) -> Self {
self.alias = alias.map(Into::into);
self
}
pub fn with_directive(mut self, directive: impl Into<Node<Directive>>) -> Self {
self.directives.push(directive.into());
self
}
pub fn with_directives(
mut self,
directives: impl IntoIterator<Item = Node<Directive>>,
) -> Self {
self.directives.extend(directives);
self
}
pub fn with_argument(mut self, name: Name, value: impl Into<Node<Value>>) -> Self {
self.arguments.push((name, value).into());
self
}
pub fn with_arguments(mut self, arguments: impl IntoIterator<Item = Node<Argument>>) -> Self {
self.arguments.extend(arguments);
self
}
pub fn with_selection(mut self, selection: impl Into<Selection>) -> Self {
self.selection_set.push(selection);
self
}
pub fn with_selections(
mut self,
selections: impl IntoIterator<Item = impl Into<Selection>>,
) -> Self {
self.selection_set.extend(selections);
self
}
pub fn response_key(&self) -> &Name {
self.alias.as_ref().unwrap_or(&self.name)
}
pub fn ty(&self) -> &Type {
&self.definition.ty
}
pub fn inner_type_def<'a>(&self, schema: &'a Schema) -> Option<&'a schema::ExtendedType> {
schema.types.get(self.ty().inner_named_type())
}
pub fn argument_by_name(&self, name: &str) -> Result<&Node<Value>, ArgumentByNameError> {
Argument::argument_by_name(&self.arguments, name, || {
self.definition
.argument_by_name(name)
.ok_or(ArgumentByNameError::NoSuchArgument)
})
}
pub fn specified_argument_by_name(&self, name: &str) -> Option<&Node<Value>> {
Argument::specified_argument_by_name(&self.arguments, name)
}
serialize_method!();
}
impl InlineFragment {
pub fn with_type_condition(type_condition: NamedType) -> Self {
let selection_set = SelectionSet::new(type_condition.clone());
Self {
type_condition: Some(type_condition),
directives: DirectiveList::new(),
selection_set,
}
}
pub fn without_type_condition(parent_selection_set_type: NamedType) -> Self {
Self {
type_condition: None,
directives: DirectiveList::new(),
selection_set: SelectionSet::new(parent_selection_set_type),
}
}
pub fn with_directive(mut self, directive: impl Into<Node<Directive>>) -> Self {
self.directives.push(directive.into());
self
}
pub fn with_directives(
mut self,
directives: impl IntoIterator<Item = Node<Directive>>,
) -> Self {
self.directives.extend(directives);
self
}
pub fn with_selection(mut self, selection: impl Into<Selection>) -> Self {
self.selection_set.push(selection);
self
}
pub fn with_selections(
mut self,
selections: impl IntoIterator<Item = impl Into<Selection>>,
) -> Self {
self.selection_set.extend(selections);
self
}
serialize_method!();
}
impl FragmentSpread {
pub fn new(fragment_name: Name) -> Self {
Self {
fragment_name,
directives: DirectiveList::new(),
}
}
pub fn with_directive(mut self, directive: impl Into<Node<Directive>>) -> Self {
self.directives.push(directive.into());
self
}
pub fn with_directives(
mut self,
directives: impl IntoIterator<Item = Node<Directive>>,
) -> Self {
self.directives.extend(directives);
self
}
pub fn fragment_def<'a>(&self, document: &'a ExecutableDocument) -> Option<&'a Node<Fragment>> {
document.fragments.get(&self.fragment_name)
}
serialize_method!();
}
impl FieldSet {
pub fn parse(
schema: &Valid<Schema>,
type_name: NamedType,
source_text: impl Into<String>,
path: impl AsRef<Path>,
) -> Result<FieldSet, WithErrors<FieldSet>> {
Parser::new().parse_field_set(schema, type_name, source_text, path)
}
pub fn parse_and_validate(
schema: &Valid<Schema>,
type_name: NamedType,
source_text: impl Into<String>,
path: impl AsRef<Path>,
) -> Result<Valid<Self>, WithErrors<Self>> {
let (field_set, mut errors) =
Parser::new().parse_field_set_inner(schema, type_name, source_text, path);
validation::validate_field_set(&mut errors, schema, &field_set);
errors.into_valid_result(field_set)
}
pub fn validate(&self, schema: &Valid<Schema>) -> Result<(), DiagnosticList> {
let mut sources = IndexMap::clone(&schema.sources);
sources.extend(self.sources.iter().map(|(k, v)| (*k, v.clone())));
let mut errors = DiagnosticList::new(Arc::new(sources));
validation::validate_field_set(&mut errors, schema, self);
errors.into_result()
}
serialize_method!();
}
impl fmt::Display for SelectionPath {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match &self.root {
ExecutableDefinitionName::AnonymousOperation(operation_type) => {
write!(f, "{operation_type}")?
}
ExecutableDefinitionName::NamedOperation(operation_type, name) => {
write!(f, "{operation_type} {name}")?
}
ExecutableDefinitionName::Fragment(name) => write!(f, "fragment {name}")?,
}
for name in &self.nested_fields {
write!(f, " → {name}")?
}
Ok(())
}
}