pub use super::ast_conversion::*;
use crate::{
error::{Error, ErrorType, Result},
ArenaHashMap, ArenaVec,
};
use bumpalo::collections::CollectIn;
pub struct ASTContext {
pub arena: bumpalo::Bump,
}
impl ASTContext {
pub fn new() -> Self {
let arena = bumpalo::Bump::new();
ASTContext { arena }
}
#[inline]
pub fn alloc<T>(&self, item: T) -> &T {
self.arena.alloc(item)
}
#[inline]
pub fn alloc_str(&self, str: &str) -> &str {
self.arena.alloc_str(str)
}
#[inline]
pub fn alloc_string(&self, str: String) -> &str {
self.arena.alloc(str)
}
}
impl Default for ASTContext {
fn default() -> Self {
Self::new()
}
}
pub type Variables<'a> = ArenaHashMap<'a, &'a str, &'a Value<'a>>;
#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
pub struct BooleanValue {
pub value: bool,
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
pub struct Variable<'a> {
pub name: &'a str,
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
pub struct EnumValue<'a> {
pub value: &'a str,
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
pub struct IntValue<'a> {
pub value: &'a str,
}
#[derive(Debug, Clone, Copy)]
pub struct FloatValue<'a> {
pub value: &'a str,
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
pub struct StringValue<'a> {
pub value: &'a str,
}
impl<'a> StringValue<'a> {
pub fn new<S: AsRef<str>>(ctx: &'a ASTContext, str: S) -> Self {
StringValue {
value: ctx.alloc_str(str.as_ref()),
}
}
#[inline]
pub fn is_block(&self) -> bool {
let mut has_newline = false;
let mut has_nonprintable = false;
for c in self.value.chars() {
match c {
'\n' => has_newline = true,
'\r' | '\t' | '\u{0020}'..='\u{FFFF}' => {}
_ => has_nonprintable = true,
}
}
has_newline && !has_nonprintable
}
}
#[derive(Debug, PartialEq, Clone)]
pub enum Value<'a> {
Variable(Variable<'a>),
String(StringValue<'a>),
Float(FloatValue<'a>),
Int(IntValue<'a>),
Boolean(BooleanValue),
Enum(EnumValue<'a>),
List(ListValue<'a>),
Object(ObjectValue<'a>),
Null,
}
impl<'a> Value<'a> {
pub fn is_truthy(&self, variables: Option<&Variables<'a>>) -> bool {
match self {
Value::Null => false,
Value::Boolean(BooleanValue { value }) => *value,
Value::Int(IntValue { value }) => {
let int = value.parse::<i32>().unwrap_or(0);
int != 0
}
Value::Float(FloatValue { value }) => {
let float = value.parse::<f64>().unwrap_or(0.0);
float != 0.0
}
Value::String(StringValue { value }) => !value.is_empty(),
Value::List(_) | Value::Object(_) | Value::Enum(_) => true,
Value::Variable(var) => variables
.and_then(|vars| vars.get(var.name))
.map(|value| value.is_truthy(None))
.unwrap_or(false),
}
}
}
#[derive(Debug, PartialEq, Clone)]
pub struct ListValue<'a> {
pub children: ArenaVec<'a, Value<'a>>,
}
impl<'a> ListValue<'a> {
#[inline]
pub fn is_empty(&self) -> bool {
self.children.is_empty()
}
}
#[derive(Debug, PartialEq, Clone)]
pub struct ObjectField<'a> {
pub name: &'a str,
pub value: Value<'a>,
}
#[derive(Debug, PartialEq, Clone)]
pub struct ObjectValue<'a> {
pub children: ArenaVec<'a, ObjectField<'a>>,
}
impl<'a> ObjectValue<'a> {
#[inline]
pub fn is_empty(&self) -> bool {
self.children.is_empty()
}
pub fn as_map(&'a self, ctx: &'a ASTContext) -> ArenaHashMap<'a, &'a str, &'a Value<'a>> {
let mut map = ArenaHashMap::new_in(&ctx.arena);
for field in self.children.iter() {
map.insert(field.name, &field.value);
}
map
}
}
#[derive(Debug, PartialEq, Clone)]
pub struct Argument<'a> {
pub name: &'a str,
pub value: Value<'a>,
}
#[derive(Debug, PartialEq, Clone)]
pub struct Arguments<'a> {
pub children: ArenaVec<'a, Argument<'a>>,
}
impl<'a> Arguments<'a> {
#[inline]
pub fn is_empty(&self) -> bool {
self.children.is_empty()
}
#[inline]
pub fn as_object_value(&'a self, ctx: &'a ASTContext) -> ObjectValue<'a> {
let new_children = self
.children
.iter()
.map(|arg| ObjectField {
name: arg.name,
value: arg.value.clone(),
})
.collect_in(&ctx.arena);
ObjectValue {
children: new_children,
}
}
pub fn as_map(&'a self, ctx: &'a ASTContext) -> ArenaHashMap<'a, &'a str, &'a Value<'a>> {
let mut map = ArenaHashMap::new_in(&ctx.arena);
for argument in self.children.iter() {
map.insert(argument.name, &argument.value);
}
map
}
}
#[derive(Debug, PartialEq, Clone)]
pub struct Directive<'a> {
pub name: &'a str,
pub arguments: Arguments<'a>,
}
#[derive(Debug, PartialEq, Clone)]
pub struct Directives<'a> {
pub children: ArenaVec<'a, Directive<'a>>,
}
impl<'a> Directives<'a> {
#[inline]
pub fn is_empty(&self) -> bool {
self.children.is_empty()
}
}
#[derive(Debug, PartialEq, Clone)]
pub struct SelectionSet<'a> {
pub selections: ArenaVec<'a, Selection<'a>>,
}
impl<'a> SelectionSet<'a> {
#[inline]
pub fn is_empty(&self) -> bool {
self.selections.is_empty()
}
}
#[derive(Debug, PartialEq, Clone)]
pub struct Field<'a> {
pub alias: Option<&'a str>,
pub name: &'a str,
pub arguments: Arguments<'a>,
pub directives: Directives<'a>,
pub selection_set: SelectionSet<'a>,
}
impl<'a> Field<'a> {
#[inline]
pub fn alias_or_name(&self) -> &'a str {
self.alias.unwrap_or(self.name)
}
#[inline]
pub fn new_leaf(ctx: &'a ASTContext, name: &'a str) -> Self {
Field {
alias: None,
name,
arguments: Arguments::default_in(&ctx.arena),
directives: Directives::default_in(&ctx.arena),
selection_set: SelectionSet::default_in(&ctx.arena),
}
}
#[inline]
pub fn new_aliased_leaf(ctx: &'a ASTContext, alias: &'a str, name: &'a str) -> Self {
Field {
alias: Some(alias), name,
arguments: Arguments::default_in(&ctx.arena),
directives: Directives::default_in(&ctx.arena),
selection_set: SelectionSet::default_in(&ctx.arena),
}
}
}
#[derive(Debug, PartialEq, Clone)]
pub struct FragmentSpread<'a> {
pub name: NamedType<'a>,
pub directives: Directives<'a>,
}
#[derive(Debug, PartialEq, Clone)]
pub struct InlineFragment<'a> {
pub type_condition: Option<NamedType<'a>>,
pub directives: Directives<'a>,
pub selection_set: SelectionSet<'a>,
}
#[derive(Debug, PartialEq, Clone)]
pub enum Selection<'a> {
Field(Field<'a>),
FragmentSpread(FragmentSpread<'a>),
InlineFragment(InlineFragment<'a>),
}
impl<'a> Selection<'a> {
#[inline]
pub fn field(&'a self) -> Option<&'a Field<'a>> {
match self {
Selection::Field(field) => Some(field),
Selection::FragmentSpread(_) => None,
Selection::InlineFragment(_) => None,
}
}
#[inline]
pub fn fragment_spread(&'a self) -> Option<&'a FragmentSpread<'a>> {
match self {
Selection::FragmentSpread(spread) => Some(spread),
Selection::Field(_) => None,
Selection::InlineFragment(_) => None,
}
}
#[inline]
pub fn inline_fragment(&'a self) -> Option<&'a InlineFragment<'a>> {
match self {
Selection::InlineFragment(fragment) => Some(fragment),
Selection::FragmentSpread(_) => None,
Selection::Field(_) => None,
}
}
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
pub struct NamedType<'a> {
pub name: &'a str,
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
pub enum Type<'a> {
NamedType(NamedType<'a>),
ListType(&'a Type<'a>),
NonNullType(&'a Type<'a>),
}
impl<'a> Type<'a> {
#[inline]
pub fn into_list(self, ctx: &'a ASTContext) -> Type<'a> {
Type::ListType(ctx.alloc(self))
}
#[inline]
pub fn into_nonnull(self, ctx: &'a ASTContext) -> Type<'a> {
Type::NonNullType(ctx.alloc(self))
}
#[inline]
pub fn of_type(&'a self) -> &'a NamedType<'a> {
match self {
Type::NamedType(of_type) => of_type,
Type::ListType(_) => self.of_type(),
Type::NonNullType(_) => self.of_type(),
}
}
}
#[derive(Debug, PartialEq, Clone)]
pub struct VariableDefinition<'a> {
pub variable: Variable<'a>,
pub of_type: Type<'a>,
pub default_value: Value<'a>,
pub directives: Directives<'a>,
}
#[derive(Debug, PartialEq, Clone)]
pub struct VariableDefinitions<'a> {
pub children: ArenaVec<'a, VariableDefinition<'a>>,
}
impl<'a> VariableDefinitions<'a> {
#[inline]
pub fn is_empty(&self) -> bool {
self.children.is_empty()
}
pub fn as_map(
&'a self,
ctx: &'a ASTContext,
) -> ArenaHashMap<'a, &'a str, &'a VariableDefinition<'a>> {
let mut map = ArenaHashMap::new_in(&ctx.arena);
for var_def in self.children.iter() {
map.insert(var_def.variable.name, var_def);
}
map
}
pub fn default_in(bump: &'a bumpalo::Bump) -> Self {
Self {
children: ArenaVec::new_in(bump),
}
}
}
#[derive(Debug, PartialEq, Clone)]
pub struct FragmentDefinition<'a> {
pub name: NamedType<'a>,
pub type_condition: NamedType<'a>,
pub directives: Directives<'a>,
pub selection_set: SelectionSet<'a>,
}
#[derive(Debug, PartialEq, Clone, Copy)]
pub(crate) struct FragmentDefinitionWithIndex<'a> {
pub fragment: &'a FragmentDefinition<'a>,
pub index: usize,
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
pub enum OperationKind {
Query,
Mutation,
Subscription,
}
#[derive(Debug, PartialEq, Clone)]
pub struct OperationDefinition<'a> {
pub operation: OperationKind,
pub name: Option<NamedType<'a>>,
pub variable_definitions: VariableDefinitions<'a>,
pub directives: Directives<'a>,
pub selection_set: SelectionSet<'a>,
}
#[derive(Debug, PartialEq, Clone)]
pub struct Document<'a> {
pub definitions: ArenaVec<'a, Definition<'a>>,
pub size_hint: usize,
}
impl<'a, 'b> Document<'a> {
#[inline]
pub fn is_empty(&self) -> bool {
self.definitions.is_empty()
}
pub(crate) fn fragments_with_index(
&'a self,
ctx: &'a ASTContext,
) -> ArenaHashMap<'a, &'a str, FragmentDefinitionWithIndex<'a>> {
let mut map = ArenaHashMap::new_in(&ctx.arena);
for (index, definition) in self.definitions.iter().enumerate() {
if let Definition::Fragment(fragment) = definition {
map.insert(
fragment.name.name,
FragmentDefinitionWithIndex { fragment, index },
);
}
}
map
}
pub fn fragments(
&'a self,
ctx: &'a ASTContext,
) -> ArenaHashMap<'a, &'a str, &'a FragmentDefinition<'a>> {
let mut map = ArenaHashMap::new_in(&ctx.arena);
for definition in self.definitions.iter() {
if let Definition::Fragment(fragment) = definition {
map.insert(fragment.name.name, fragment);
}
}
map
}
pub(crate) fn operation_with_index(
&'a self,
by_name: Option<&'b str>,
) -> Result<(&'a OperationDefinition<'a>, usize)> {
if let Some(by_name) = by_name {
self.definitions
.iter()
.enumerate()
.find_map(|(index, definition)| match definition {
Definition::Operation(
operation @ OperationDefinition {
name: Some(NamedType { name }),
..
},
) if *name == by_name => Some((operation, index)),
_ => None,
})
.ok_or(Error::new(
format!("Operation with name {by_name} does not exist"),
Some(ErrorType::GraphQL),
))
} else {
let operations = self
.definitions
.iter()
.enumerate()
.filter_map(|(index, definition)| {
definition.operation().map(|operation| (operation, index))
})
.collect::<std::vec::Vec<(&'a OperationDefinition, usize)>>();
match operations.len() {
0 => Err(Error::new(
"Document does not contain any operations",
Some(ErrorType::GraphQL),
)),
1 => Ok(operations[0]),
_ => Err(Error::new(
"Document contains more than one operation, missing operation name",
Some(ErrorType::GraphQL),
)),
}
}
}
pub fn operation(&'a self, by_name: Option<&'b str>) -> Result<&'a OperationDefinition<'a>> {
Ok(self.operation_with_index(by_name)?.0)
}
}
#[derive(Debug, PartialEq, Clone)]
pub enum Definition<'a> {
Operation(OperationDefinition<'a>),
Fragment(FragmentDefinition<'a>),
}
impl<'a> Definition<'a> {
#[inline]
pub fn operation(&'a self) -> Option<&'a OperationDefinition<'a>> {
match self {
Definition::Operation(operation) => Some(operation),
Definition::Fragment(_) => None,
}
}
#[inline]
pub fn fragment(&'a self) -> Option<&'a FragmentDefinition<'a>> {
match self {
Definition::Fragment(fragment) => Some(fragment),
Definition::Operation(_) => None,
}
}
}
pub trait WithDirectives<'arena> {
fn directives(&self) -> &Directives<'arena>;
}
macro_rules! with_directives {
($($for_type:ident),+) => {
$(
impl<'arena> WithDirectives<'arena> for $for_type<'arena> {
#[inline]
fn directives(&self) -> &Directives<'arena> {
&self.directives
}
}
)+
};
}
with_directives!(
Field,
FragmentSpread,
InlineFragment,
OperationDefinition,
FragmentDefinition,
VariableDefinition
);
impl<'arena> WithDirectives<'arena> for Selection<'arena> {
fn directives(&self) -> &Directives<'arena> {
match self {
Selection::Field(field) => &field.directives,
Selection::FragmentSpread(spread) => &spread.directives,
Selection::InlineFragment(fragment) => &fragment.directives,
}
}
}
impl<'arena> WithDirectives<'arena> for Definition<'arena> {
#[inline]
fn directives(&self) -> &Directives<'arena> {
match self {
Definition::Operation(operation) => &operation.directives,
Definition::Fragment(fragment) => &fragment.directives,
}
}
}
pub trait Skippable<'arena>: WithDirectives<'arena> {
#[inline]
fn should_include(&'arena self, variables: Option<&Variables<'arena>>) -> bool {
for directive in self.directives().children.iter() {
if directive.name != "skip" && directive.name != "include" {
continue;
}
let if_arg = directive
.arguments
.children
.iter()
.find(|arg| arg.name == "if")
.map(|arg| arg.value.is_truthy(variables));
if let Some(if_arg) = if_arg {
return (directive.name == "include") == if_arg;
}
}
true
}
}
impl<'arena> Skippable<'arena> for Selection<'arena> {}
impl<'arena> Skippable<'arena> for Field<'arena> {}
impl<'arena> Skippable<'arena> for InlineFragment<'arena> {}
impl<'arena> Skippable<'arena> for FragmentSpread<'arena> {}
#[cfg(test)]
mod tests {
use super::{ASTContext, Document};
use crate::ast::{ParseNode, PrintNode};
#[test]
fn operation_no_operations() {
let ctx = ASTContext::new();
let ast = Document::parse(&ctx, r#"fragment Foo on Query { hello }"#).unwrap();
assert_eq!(
ast.operation(Some("queryName")).unwrap_err().message,
"Operation with name queryName does not exist"
);
assert_eq!(
ast.operation(None).unwrap_err().message,
"Document does not contain any operations"
);
}
#[test]
fn operation_one_operation() {
let ctx = ASTContext::new();
let ast = Document::parse(&ctx, r#"query queryName { hello }"#).unwrap();
assert_eq!(
ast.operation(Some("queryName")).unwrap().print(),
"query queryName {\n hello\n}"
);
assert_eq!(
ast.operation(None).unwrap().print(),
"query queryName {\n hello\n}"
);
}
#[test]
fn operation_one_operation_anonymous() {
let ctx = ASTContext::new();
let ast = Document::parse(&ctx, r#"{ hello }"#).unwrap();
assert_eq!(
ast.operation(Some("queryName")).unwrap_err().message,
"Operation with name queryName does not exist"
);
assert_eq!(ast.operation(None).unwrap().print(), "{\n hello\n}");
}
#[test]
fn operation_two_operations() {
let ctx = ASTContext::new();
let ast = Document::parse(
&ctx,
r#"query queryName { hello } query otherName { world }"#,
)
.unwrap();
assert_eq!(
ast.operation(Some("queryName")).unwrap().print(),
"query queryName {\n hello\n}"
);
assert_eq!(
ast.operation(Some("otherName")).unwrap().print(),
"query otherName {\n world\n}"
);
assert_eq!(
ast.operation(Some("badName")).unwrap_err().message,
"Operation with name badName does not exist"
);
assert_eq!(
ast.operation(None).unwrap_err().message,
"Document contains more than one operation, missing operation name"
);
}
#[test]
fn operation_two_operations_one_anonymous() {
let ctx = ASTContext::new();
let ast = Document::parse(&ctx, r#"{ hello } query otherName { world }"#).unwrap();
assert_eq!(
ast.operation(Some("queryName")).unwrap_err().message,
"Operation with name queryName does not exist"
);
assert_eq!(
ast.operation(Some("otherName")).unwrap().print(),
"query otherName {\n world\n}"
);
assert_eq!(
ast.operation(None).unwrap_err().message,
"Document contains more than one operation, missing operation name"
);
}
}