graphql_query/ast/
ast.rs

1pub use super::ast_conversion::*;
2use crate::error::{Error, ErrorType, Result};
3use bumpalo::collections::CollectIn;
4use hashbrown::{HashMap, hash_map::DefaultHashBuilder};
5
6/// A context for a GraphQL document which holds an arena allocator.
7///
8/// For the duration of parsing, storing, validating, traversing, and printing an AST its
9/// performant and convenient to allocate memory in one chunk for the AST's operations. This
10/// context represents the lifetime of an AST and its derivatives.
11///
12/// An AST Context in other words represents the memory a query and the operations you perform on
13/// it take up. This is efficient since once you're done with the query this entire allocated
14/// memory can be dropped all at once. Hence however, it's inadvisable to reuse the AST Context
15/// across multiple incoming GraphQL requests.
16pub struct ASTContext {
17    /// An arena allocator that holds the memory allocated for the AST Context's lifetime
18    pub arena: bumpalo::Bump,
19}
20
21impl ASTContext {
22    /// Create a new AST context with a preallocated arena.
23    pub fn new() -> Self {
24        let arena = bumpalo::Bump::new();
25        ASTContext { arena }
26    }
27
28    /// Put the value of `item` onto the arena and return a reference to it.
29    #[inline]
30    pub fn alloc<T>(&self, item: T) -> &T {
31        self.arena.alloc(item)
32    }
33
34    /// Allocate an `&str` slice onto the arena and return a reference to it.
35    ///
36    /// This is useful when the original slice has an undefined lifetime.
37    /// This is typically unnecessary for static slices (`&'static str`) whose lifetimes are as
38    /// long as the running program and don't need to be allocated dynamically.
39    #[inline]
40    pub fn alloc_str(&self, str: &str) -> &str {
41        self.arena.alloc_str(str)
42    }
43
44    /// Puts a `String` onto the arena and returns a reference to it to tie the `String`'s lifetime
45    /// to this AST context without reallocating or copying it.
46    #[inline]
47    pub fn alloc_string(&self, str: String) -> &str {
48        self.arena.alloc(str)
49    }
50}
51
52impl Default for ASTContext {
53    fn default() -> Self {
54        Self::new()
55    }
56}
57
58/// Map of AST Values for GraphQL Variables
59///
60/// [Reference](https://spec.graphql.org/October2021/#sec-Coercing-Variable-Values)
61pub type Variables<'a> = HashMap<&'a str, Value<'a>, DefaultHashBuilder, &'a bumpalo::Bump>;
62
63/// AST Node of a boolean value
64///
65/// [Reference](https://spec.graphql.org/October2021/#sec-Boolean-Value)
66#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
67pub struct BooleanValue {
68    pub value: bool,
69}
70
71/// AST Node of a variable identifier value.
72///
73/// These are identifiers prefixed with a `$` sign, typically in variable definitions.
74///
75/// [Reference](https://spec.graphql.org/October2021/#sec-Language.Variables)
76#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
77pub struct Variable<'a> {
78    pub name: &'a str,
79}
80
81/// AST Node of an enum value.
82///
83/// These are typically written in all caps and snake case, e.g. "`MOBILE_WEB`".
84///
85/// [Reference](https://spec.graphql.org/October2021/#sec-Enum-Value)
86#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
87pub struct EnumValue<'a> {
88    pub value: &'a str,
89}
90
91/// AST Node of an integer value.
92///
93/// Integers in GraphQL are limited to 32-bit signed, non-fractional values.
94///
95/// [Reference](https://spec.graphql.org/October2021/#sec-Int)
96#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
97pub struct IntValue<'a> {
98    pub value: &'a str,
99}
100
101/// AST Node of a floating point value.
102///
103/// Floats in GraphQL are signed, double precision values as defined by [IEEE 754](https://en.wikipedia.org/wiki/IEEE_754).
104/// They are however limited to finite values only.
105/// [Reference](https://spec.graphql.org/October2021/#sec-Float)
106#[derive(Debug, Clone, Copy)]
107pub struct FloatValue<'a> {
108    pub value: &'a str,
109}
110
111/// AST Node of a string value.
112///
113/// GraphQL has a number of escaped characters that are normalised away when parsing and
114/// hence this `value` is expected to not contain escaped characters.
115/// The strings in GraphQL can be compared to JSON Unicode strings.
116/// [Reference](https://spec.graphql.org/October2021/#sec-String)
117#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
118pub struct StringValue<'a> {
119    pub value: &'a str,
120}
121
122impl<'a> StringValue<'a> {
123    pub fn new<S: AsRef<str>>(ctx: &'a ASTContext, str: S) -> Self {
124        StringValue {
125            value: ctx.alloc_str(str.as_ref()),
126        }
127    }
128
129    /// Determines whether a string should be printed as a block string
130    /// rather than a regular single-line string.
131    #[inline]
132    pub fn is_block(&self) -> bool {
133        let mut has_newline = false;
134        let mut has_nonprintable = false;
135        for c in self.value.chars() {
136            match c {
137                '\n' => has_newline = true,
138                '\r' | '\t' | '\u{0020}'..='\u{FFFF}' => {}
139                _ => has_nonprintable = true,
140            }
141        }
142        has_newline && !has_nonprintable
143    }
144}
145
146/// AST Node of possible input values in GraphQL.
147///
148/// Fields and Directives accept input values as arguments.
149///
150/// [Reference](https://spec.graphql.org/October2021/#sec-Input-Values)
151#[derive(Debug, PartialEq, Clone)]
152pub enum Value<'a> {
153    Variable(Variable<'a>),
154    String(StringValue<'a>),
155    Float(FloatValue<'a>),
156    Int(IntValue<'a>),
157    Boolean(BooleanValue),
158    Enum(EnumValue<'a>),
159    List(ListValue<'a>),
160    Object(ObjectValue<'a>),
161    /// Representing JSON-like `null` values or the absence of a value
162    Null,
163}
164
165impl<'a> Value<'a> {
166    pub fn is_truthy(&self, variables: Option<&Variables<'a>>) -> bool {
167        match self {
168            Value::Null => false,
169            Value::Boolean(BooleanValue { value }) => *value,
170            Value::Int(IntValue { value }) => {
171                let int = value.parse::<i32>().unwrap_or(0);
172                int != 0
173            }
174            Value::Float(FloatValue { value }) => {
175                let float = value.parse::<f64>().unwrap_or(0.0);
176                float != 0.0
177            }
178            Value::String(StringValue { value }) => !value.is_empty(),
179            Value::List(_) | Value::Object(_) | Value::Enum(_) => true,
180            Value::Variable(var) => variables
181                .and_then(|vars| vars.get(var.name))
182                .map(|value| value.is_truthy(None))
183                .unwrap_or(false),
184        }
185    }
186}
187
188/// AST Node for a List of values.
189///
190/// Lists in GraphQL are ordered sequences and serialize to JSON arrays. Its
191/// contents may be any arbitrary value literal or variable.
192/// [Reference](https://spec.graphql.org/October2021/#sec-List-Value)
193#[derive(Debug, PartialEq, Clone)]
194pub struct ListValue<'a> {
195    pub children: bumpalo::collections::Vec<'a, Value<'a>>,
196}
197
198impl<'a> ListValue<'a> {
199    /// Checks whether this List contains any values.
200    #[inline]
201    pub fn is_empty(&self) -> bool {
202        self.children.is_empty()
203    }
204}
205
206/// AST Node for a field of an Object value.
207///
208/// Objects in GraphQL are unordered lists of keyed input values and serialize to JSON objects.
209/// An Object literal's contents may be any arbitrary value literal or variable.
210/// [Reference](https://spec.graphql.org/October2021/#ObjectField)
211#[derive(Debug, PartialEq, Clone)]
212pub struct ObjectField<'a> {
213    pub name: &'a str,
214    pub value: Value<'a>,
215}
216
217/// AST Node for an Object value, which is a list of Object fields.
218///
219/// Objects in GraphQL are unordered lists of keyed input values and serialize to JSON objects.
220/// An Object literal's contents may be any arbitrary value literal or variable.
221/// [Reference](https://spec.graphql.org/October2021/#sec-Input-Object-Values)
222#[derive(Debug, PartialEq, Clone)]
223pub struct ObjectValue<'a> {
224    pub children: bumpalo::collections::Vec<'a, ObjectField<'a>>,
225}
226
227impl<'a> ObjectValue<'a> {
228    /// Checks whether this Object contains any fields.
229    #[inline]
230    pub fn is_empty(&self) -> bool {
231        self.children.is_empty()
232    }
233
234    /// Returns a `Map` keyed by all object field's names mapped to their values.
235    pub fn as_map(
236        &'a self,
237        ctx: &'a ASTContext,
238    ) -> HashMap<&str, &Value<'a>, DefaultHashBuilder, &'a bumpalo::Bump> {
239        let mut map = HashMap::new_in(&ctx.arena);
240        for field in self.children.iter() {
241            map.insert(field.name, &field.value);
242        }
243        map
244    }
245}
246
247/// AST Node for an Argument, which carries a name and a value.
248///
249/// Arguments in GraphQL are unordered lists of inputs to a field's or directive's arguments.
250/// [Reference](https://spec.graphql.org/October2021/#Argument)
251#[derive(Debug, PartialEq, Clone)]
252pub struct Argument<'a> {
253    pub name: &'a str,
254    pub value: Value<'a>,
255}
256
257/// AST Node for a list of Arguments, which are similar to parameterized inputs to a function.
258///
259/// Arguments in GraphQL are unordered lists of inputs to a field's or directive's arguments.
260/// [Reference](https://spec.graphql.org/October2021/#Arguments)
261#[derive(Debug, PartialEq, Clone)]
262pub struct Arguments<'a> {
263    pub children: bumpalo::collections::Vec<'a, Argument<'a>>,
264}
265
266impl<'a> Arguments<'a> {
267    /// Checks whether this list of Arguments contains any values.
268    #[inline]
269    pub fn is_empty(&self) -> bool {
270        self.children.is_empty()
271    }
272
273    /// Converts `Arguments` into an `ObjectValue`
274    #[inline]
275    pub fn as_object_value(&'a self, ctx: &'a ASTContext) -> ObjectValue<'a> {
276        let new_children = self
277            .children
278            .iter()
279            .map(|arg| ObjectField {
280                name: arg.name,
281                value: arg.value.clone(),
282            })
283            .collect_in(&ctx.arena);
284
285        ObjectValue {
286            children: new_children,
287        }
288    }
289
290    /// Returns a `Map` keyed by all arguments' names mapped to their values.
291    pub fn as_map(
292        &'a self,
293        ctx: &'a ASTContext,
294    ) -> HashMap<&str, &Value<'a>, DefaultHashBuilder, &'a bumpalo::Bump> {
295        let mut map = HashMap::new_in(&ctx.arena);
296        for argument in self.children.iter() {
297            map.insert(argument.name, &argument.value);
298        }
299        map
300    }
301}
302
303/// AST Node for GraphQL Directives, which provide a way to describe alternate behavior in GraphQL.
304///
305/// Typical directives that occur in queries are for example `@skip`, @include`, and `@defer`.
306/// [Reference](https://spec.graphql.org/October2021/#sec-Language.Directives)
307#[derive(Debug, PartialEq, Clone)]
308pub struct Directive<'a> {
309    pub name: &'a str,
310    pub arguments: Arguments<'a>,
311}
312
313/// AST Node for lists of GraphQL Directives, which provide a way to describe alternate behavior in GraphQL.
314///
315/// Typical directives that occur in queries are for example `@skip`, @include`, and `@defer`.
316/// [Reference](https://spec.graphql.org/October2021/#sec-Language.Directives)
317#[derive(Debug, PartialEq, Clone)]
318pub struct Directives<'a> {
319    pub children: bumpalo::collections::Vec<'a, Directive<'a>>,
320}
321
322impl<'a> Directives<'a> {
323    /// Checks whether this list of Directives contains any values.
324    #[inline]
325    pub fn is_empty(&self) -> bool {
326        self.children.is_empty()
327    }
328}
329
330/// AST Node for Selection Sets, which provide a way to select more information on a given parent.
331///
332/// [Reference](https://spec.graphql.org/October2021/#sec-Selection-Sets)
333#[derive(Debug, PartialEq, Clone)]
334pub struct SelectionSet<'a> {
335    pub selections: bumpalo::collections::Vec<'a, Selection<'a>>,
336}
337
338impl<'a> SelectionSet<'a> {
339    /// Checks whether this Selection Set contains any selections.
340    #[inline]
341    pub fn is_empty(&self) -> bool {
342        self.selections.is_empty()
343    }
344}
345
346/// AST Node for Fields, which can be likened to functions or properties on a parent object.
347///
348/// In JSON this would represent a property in a JSON object.
349/// [Reference](https://spec.graphql.org/October2021/#sec-Language.Fields)
350#[derive(Debug, PartialEq, Clone)]
351pub struct Field<'a> {
352    /// A Field's `alias`, which is used to request information under a different name than the
353    /// Field's `name`.
354    /// [Reference](https://spec.graphql.org/October2021/#sec-Field-Alias)
355    pub alias: Option<&'a str>,
356    /// A Field's `name`, which represents a resolver on a GraphQL schema's object type.
357    pub name: &'a str,
358    /// Arguments that are passed to a Field.
359    ///
360    /// When no Arguments are passed, this will be an empty
361    /// list, as can be checked using `Arguments::is_empty`.
362    /// See: [Arguments]
363    pub arguments: Arguments<'a>,
364    /// Directives that are annotating this Field.
365    ///
366    /// When no Directives are present, this will be an empty
367    /// list, as can be checked using `Directives::is_empty`.
368    /// See: [Directives]
369    pub directives: Directives<'a>,
370    /// A sub-Selection Set that is passed below this field to add selections to this field's
371    /// returned GraphQL object type.
372    ///
373    /// When no selections are present, this will be an empty
374    /// list, as can be checked using `SelectionSet::is_empty`.
375    /// See: [SelectionSet]
376    pub selection_set: SelectionSet<'a>,
377}
378
379impl<'a> Field<'a> {
380    /// Get the alias of the field, if present, otherwise get the name.
381    #[inline]
382    pub fn alias_or_name(&self) -> &'a str {
383        self.alias.unwrap_or(self.name)
384    }
385
386    /// Creates a new leaf field with the given `name`.
387    ///
388    /// All sub-lists, like `arguments`, `directives` and `selection_set` will be created as empty
389    /// defaults.
390    #[inline]
391    pub fn new_leaf(ctx: &'a ASTContext, name: &'a str) -> Self {
392        Field {
393            alias: None,
394            name,
395            arguments: Arguments::default_in(&ctx.arena),
396            directives: Directives::default_in(&ctx.arena),
397            selection_set: SelectionSet::default_in(&ctx.arena),
398        }
399    }
400
401    /// Creates a new leaf field with the given `name` and `alias`.
402    ///
403    /// All sub-lists, like `arguments`, `directives` and `selection_set` will be created as empty
404    /// defaults.
405    #[inline]
406    pub fn new_aliased_leaf(ctx: &'a ASTContext, alias: &'a str, name: &'a str) -> Self {
407        Field {
408            // NOTE: Optimise future cases of checking for aliases by pointer
409            alias: Some(alias), //if alias == name { name } else { alias },
410            name,
411            arguments: Arguments::default_in(&ctx.arena),
412            directives: Directives::default_in(&ctx.arena),
413            selection_set: SelectionSet::default_in(&ctx.arena),
414        }
415    }
416}
417
418/// AST Node for a Fragment Spread, which refers to a [`FragmentDefinition`] with an additional
419/// [`SelectionSet`].
420///
421/// [Reference](https://spec.graphql.org/October2021/#sec-Language.Fragments)
422#[derive(Debug, PartialEq, Clone)]
423pub struct FragmentSpread<'a> {
424    /// A given name of the [FragmentDefinition] that must be spread in place of this Fragment
425    /// Spread on a GraphQL API.
426    pub name: NamedType<'a>,
427    /// Directives that are annotating this Fragment Spread.
428    ///
429    /// When no Directives are present, this will be an empty
430    /// list, as can be checked using `Directives::is_empty`.
431    /// See: [Directives]
432    pub directives: Directives<'a>,
433}
434
435/// AST Node for an inline Fragment definition with an additional [`SelectionSet`].
436/// This may only be applied when the type condition matches or when no type condition is present.
437///
438/// [Reference](https://spec.graphql.org/October2021/#sec-Language.Fragments)
439#[derive(Debug, PartialEq, Clone)]
440pub struct InlineFragment<'a> {
441    /// A given type condition's type name that must match before this fragment is applied on a
442    /// GraphQL API. On inline fragments this is optional and no type condition has to be passed.
443    pub type_condition: Option<NamedType<'a>>,
444    /// Directives that are annotating this Inline Fragment.
445    ///
446    /// When no Directives are present, this will be an empty
447    /// list, as can be checked using `Directives::is_empty`.
448    /// See: [Directives]
449    pub directives: Directives<'a>,
450    /// A sub-Selection Set that is applied when this Fragment is applied to the parent
451    /// Selection Set.
452    /// See: [SelectionSet]
453    pub selection_set: SelectionSet<'a>,
454}
455
456/// AST Node of a selection as contained inside a [`SelectionSet`].
457///
458/// Any given Selection Set may contain fields, fragment spread, and inline fragments.
459/// [Reference](https://spec.graphql.org/October2021/#Selection)
460#[derive(Debug, PartialEq, Clone)]
461pub enum Selection<'a> {
462    Field(Field<'a>),
463    FragmentSpread(FragmentSpread<'a>),
464    InlineFragment(InlineFragment<'a>),
465}
466
467impl<'a> Selection<'a> {
468    /// Helper method to return the [`Field`] if the Selection is a `Field`.
469    #[inline]
470    pub fn field(&'a self) -> Option<&'a Field<'a>> {
471        match self {
472            Selection::Field(field) => Some(&field),
473            Selection::FragmentSpread(_) => None,
474            Selection::InlineFragment(_) => None,
475        }
476    }
477
478    /// Helper method to return the [`FragmentSpread`] if the Selection is a `FragmentSpread`.
479    #[inline]
480    pub fn fragment_spread(&'a self) -> Option<&'a FragmentSpread<'a>> {
481        match self {
482            Selection::FragmentSpread(spread) => Some(&spread),
483            Selection::Field(_) => None,
484            Selection::InlineFragment(_) => None,
485        }
486    }
487
488    /// Helper method to return the [`InlineFragment`] if the Selection is an `InlineFragment`.
489    #[inline]
490    pub fn inline_fragment(&'a self) -> Option<&'a InlineFragment<'a>> {
491        match self {
492            Selection::InlineFragment(fragment) => Some(&fragment),
493            Selection::FragmentSpread(_) => None,
494            Selection::Field(_) => None,
495        }
496    }
497}
498
499/// AST Node for a type name.
500///
501/// This AST uses this reference instead of a raw `&str`.
502/// slice whenever the AST refers to a concrete object type, input type, fragment
503/// name, or operation name.
504#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
505pub struct NamedType<'a> {
506    pub name: &'a str,
507}
508
509/// AST Node for a type reference.
510///
511/// [`VariableDefinitions`] must describe their type when they're defined, including whether they expect
512/// lists, non-null values, or a type reference, which is a recursive type definition.
513/// [Reference](https://spec.graphql.org/October2021/#sec-Type-References)
514#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
515pub enum Type<'a> {
516    /// A reference to a named input type, which is a leaf node of a [Type].
517    NamedType(NamedType<'a>),
518    /// A list node wrapper for a Type, which indicates that a GraphQL API will always pass a list of the
519    /// contained type in place.
520    ListType(&'a Type<'a>),
521    /// A non-null node wrapper for a Type, which indicates that a GraphQL API may not pass `null` instead
522    /// of the conained type.
523    NonNullType(&'a Type<'a>),
524}
525
526impl<'a> Type<'a> {
527    /// Wraps this type in a list, indicating that it expects the current Type to be a list of
528    /// itself instead.
529    #[inline]
530    pub fn into_list(self, ctx: &'a ASTContext) -> Type<'a> {
531        Type::ListType(ctx.alloc(self))
532    }
533
534    /// A non-null node wrapper for a Type, indicating that a GraphQL API may not pass `null` instead
535    /// of the conained type.
536    #[inline]
537    pub fn into_nonnull(self, ctx: &'a ASTContext) -> Type<'a> {
538        Type::NonNullType(ctx.alloc(self))
539    }
540
541    /// Unwraps a Type recursively and returns the `NamedType` that is contained within its
542    /// wrappers.
543    #[inline]
544    pub fn of_type(&'a self) -> &'a NamedType<'a> {
545        match self {
546            Type::NamedType(of_type) => of_type,
547            Type::ListType(_) => self.of_type(),
548            Type::NonNullType(_) => self.of_type(),
549        }
550    }
551}
552
553/// AST Node for a variable definition.
554///
555/// A variable definition defines multiple [Variable]
556/// identifiers that can be used in place of any other non-static [Value] throughout the
557/// document.
558///
559/// [Reference](https://spec.graphql.org/October2021/#VariableDefinition)
560#[derive(Debug, PartialEq, Clone)]
561pub struct VariableDefinition<'a> {
562    /// The variable's name, as in, its identifier, which is prefixed with a `$` sign in the
563    /// document.
564    pub variable: Variable<'a>,
565    /// Annotation of the type of a given variable, which ultimately leads to a type reference of
566    /// an input type, as defined on a GraphQL schema.
567    pub of_type: Type<'a>,
568    /// A GraphQL variable may be replaced by a default value, when it's not passed or `null`
569    /// is passed for a non-null variable. When this definition doesn't contain any default value
570    /// this property is set to `Value::Null`.
571    pub default_value: Value<'a>,
572    /// Directives that are annotating this Variable Definition.
573    ///
574    /// When no Directives are present, this will be an empty
575    /// list, as can be checked using `Directives::is_empty`.
576    /// See: [Directives]
577    pub directives: Directives<'a>,
578}
579
580#[derive(Debug, PartialEq, Clone)]
581pub struct VariableDefinitions<'a> {
582    pub children: bumpalo::collections::Vec<'a, VariableDefinition<'a>>,
583}
584
585impl<'a> VariableDefinitions<'a> {
586    /// Checks whether the list of Variable Definitions is empty.
587    #[inline]
588    pub fn is_empty(&self) -> bool {
589        self.children.is_empty()
590    }
591
592    /// Returns a `Map` keyed by all variable names mapped to their definitions.
593    pub fn as_map(
594        &'a self,
595        ctx: &'a ASTContext,
596    ) -> HashMap<&str, &'a VariableDefinition<'a>, DefaultHashBuilder, &'a bumpalo::Bump> {
597        let mut map = HashMap::new_in(&ctx.arena);
598        for var_def in self.children.iter() {
599            map.insert(var_def.variable.name, var_def);
600        }
601        map
602    }
603}
604
605/// AST Node for a Fragment definition with an additional Selection Set.
606///
607/// This may only be applied when the type condition matches or when no type condition is present
608/// and extends a Selection Set by being applied using a [`FragmentSpread`] selection.
609/// [Reference](https://spec.graphql.org/October2021/#sec-Language.Fragments)
610#[derive(Debug, PartialEq, Clone)]
611pub struct FragmentDefinition<'a> {
612    /// A given name of the Fragment Definition that is used by [FragmentSpread] selections to
613    /// refer to this definition.
614    pub name: NamedType<'a>,
615    /// A given type condition's type name that must match before this fragment is applied on a
616    /// GraphQL API. On inline fragments this is optional and no type condition has to be passed.
617    pub type_condition: NamedType<'a>,
618    /// Directives that are annotating this Fragment.
619    ///
620    /// When no Directives are present, this will be an empty
621    /// list, as can be checked using `Directives::is_empty`.
622    /// See: [Directives]
623    pub directives: Directives<'a>,
624    /// A sub-Selection Set that is applied when this Fragment is applied to the parent
625    /// Selection Set.
626    /// See: [SelectionSet]
627    pub selection_set: SelectionSet<'a>,
628}
629
630/// A wrapper around the [`FragmentDefinition`] struct that also contains the index of the fragment
631/// definition within the list of definitions in a given [`Document`].
632#[derive(Debug, PartialEq, Clone, Copy)]
633pub(crate) struct FragmentDefinitionWithIndex<'a> {
634    pub fragment: &'a FragmentDefinition<'a>,
635    pub index: usize,
636}
637
638/// AST Node for a kind of operation, as referred to by an [`OperationDefinition`].
639///
640/// In GraphQL there are three different operations, with each having a unique identifier on
641/// Operation Definitions.
642/// [Reference](https://spec.graphql.org/October2021/#sec-Language.Operations)
643#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
644pub enum OperationKind {
645    Query,
646    Mutation,
647    Subscription,
648}
649
650/// AST Node for an Operation Definition, which defines the entrypoint for GraphQL's execution.
651///
652/// [Reference](https://spec.graphql.org/October2021/#sec-Language.Operations)
653#[derive(Debug, PartialEq, Clone)]
654pub struct OperationDefinition<'a> {
655    /// The kind of operation that this definition specifies
656    pub operation: OperationKind,
657    // An optional name, as given to the operation definition.
658    //
659    // A [Document] may contain multiple
660    // Operation Definitions from which a single one can be selected during execution. When a
661    // Document contains only a single operation, it doesn't have to have a name.
662    pub name: Option<NamedType<'a>>,
663    /// A list of variables that the operation defines and accepts during execution.
664    ///
665    /// When an
666    /// Operation Definition defines no variables this may be an empty list, as can be checked
667    /// using `Directives::is_empty`.
668    pub variable_definitions: VariableDefinitions<'a>,
669    /// Directives that are annotating this Operation Definition.
670    ///
671    /// When no Directives are present, this will be an empty
672    /// list, as can be checked using `Directives::is_empty`.
673    /// See: [Directives]
674    pub directives: Directives<'a>,
675    /// A sub-Selection Set that is applied when this Operation Definition is executed to the root
676    /// type of the specified kind of operation.
677    /// See: [SelectionSet]
678    pub selection_set: SelectionSet<'a>,
679}
680
681/// AST Root Node for a GraphQL query language document. This contains one or more definitions of
682/// fragments or operations.
683///
684/// [Reference](https://spec.graphql.org/October2021/#sec-Document)
685#[derive(Debug, PartialEq, Clone)]
686pub struct Document<'a> {
687    pub definitions: bumpalo::collections::Vec<'a, Definition<'a>>,
688    /// A hint on how large the source text was from which this Document was parsed.
689    ///
690    /// This gives an initial indication of the starting capacity of a `String` that will hold the stringified
691    /// document.
692    pub size_hint: usize,
693}
694
695impl<'a, 'b> Document<'a> {
696    /// Checks whether this document contains any definitions.
697    #[inline]
698    pub fn is_empty(&self) -> bool {
699        self.definitions.is_empty()
700    }
701
702    /// Returns a `Map` keyed by all fragment names mapped to their [`FragmentDefinition`] and the
703    /// index of where they appear in the list of definitions of the given [`Document`].
704    /// This is useful for manually traversing the document and resolving [`FragmentSpread`] nodes to
705    /// their definitions.
706    pub(crate) fn fragments_with_index(
707        &'a self,
708        ctx: &'a ASTContext,
709    ) -> hashbrown::HashMap<&str, FragmentDefinitionWithIndex<'a>, DefaultHashBuilder, &bumpalo::Bump>
710    {
711        let mut map = hashbrown::HashMap::new_in(&ctx.arena);
712        for (index, definition) in self.definitions.iter().enumerate() {
713            if let Definition::Fragment(fragment) = definition {
714                map.insert(
715                    fragment.name.name,
716                    FragmentDefinitionWithIndex { fragment, index },
717                );
718            }
719        }
720        map
721    }
722
723    /// Returns a `Map` keyed by all fragment names mapped to their fragment definitions.
724    /// This is useful for manually traversing the document and resolving [`FragmentSpread`] nodes to
725    /// their definitions.
726    pub fn fragments(
727        &'a self,
728        ctx: &'a ASTContext,
729    ) -> HashMap<&str, &'a FragmentDefinition<'a>, DefaultHashBuilder, &'a bumpalo::Bump> {
730        let mut map = HashMap::new_in(&ctx.arena);
731        for definition in self.definitions.iter() {
732            if let Definition::Fragment(fragment) = definition {
733                map.insert(fragment.name.name, fragment);
734            }
735        }
736        map
737    }
738
739    pub(crate) fn operation_with_index(
740        &'a self,
741        by_name: Option<&'b str>,
742    ) -> Result<(&'a OperationDefinition<'a>, usize)> {
743        if let Some(by_name) = by_name {
744            self.definitions
745                .iter()
746                .enumerate()
747                .find_map(|(index, definition)| match definition {
748                    Definition::Operation(
749                        operation @ OperationDefinition {
750                            name: Some(NamedType { name }),
751                            ..
752                        },
753                    ) if *name == by_name => Some((operation, index)),
754                    _ => None,
755                })
756                .ok_or(Error::new(
757                    format!("Operation with name {by_name} does not exist"),
758                    Some(ErrorType::GraphQL),
759                ))
760        } else {
761            let operations = self
762                .definitions
763                .iter()
764                .enumerate()
765                .filter_map(|(index, definition)| {
766                    definition.operation().map(|operation| (operation, index))
767                })
768                .collect::<std::vec::Vec<(&'a OperationDefinition, usize)>>();
769            match operations.len() {
770                0 => Err(Error::new(
771                    "Document does not contain any operations",
772                    Some(ErrorType::GraphQL),
773                )),
774                1 => Ok(operations[0]),
775                _ => Err(Error::new(
776                    "Document contains more than one operation, missing operation name",
777                    Some(ErrorType::GraphQL),
778                )),
779            }
780        }
781    }
782
783    /// Finds an operation definition by name or the single operation contained in the document
784    /// when `None` is passed.
785    ///
786    /// [Reference](https://spec.graphql.org/October2021/#GetOperation())
787    pub fn operation(&'a self, by_name: Option<&'b str>) -> Result<&'a OperationDefinition<'a>> {
788        Ok(self.operation_with_index(by_name)?.0)
789    }
790}
791
792/// AST Node for a Definition inside a query language document, which may either be an Operation
793/// Definition or a Fragment Definition.
794///
795/// [Reference](https://spec.graphql.org/October2021/#sec-Document)
796#[derive(Debug, PartialEq, Clone)]
797pub enum Definition<'a> {
798    Operation(OperationDefinition<'a>),
799    Fragment(FragmentDefinition<'a>),
800}
801
802impl<'a> Definition<'a> {
803    /// Helper method to return the [`OperationDefinition`] if the Definition is an `OperationDefinition`.
804    #[inline]
805    pub fn operation(&'a self) -> Option<&'a OperationDefinition<'a>> {
806        match self {
807            Definition::Operation(operation) => Some(operation),
808            Definition::Fragment(_) => None,
809        }
810    }
811
812    /// Helper method to return the [`FragmentDefinition`] if the Definition is a `FragmentDefinition`.
813    #[inline]
814    pub fn fragment(&'a self) -> Option<&'a FragmentDefinition<'a>> {
815        match self {
816            Definition::Fragment(fragment) => Some(fragment),
817            Definition::Operation(_) => None,
818        }
819    }
820}
821
822/// Trait implemented by all ast nodes that can have directives attached.
823pub trait WithDirectives<'arena> {
824    fn directives(&self) -> &Directives<'arena>;
825}
826
827macro_rules! with_directives {
828    ($($for_type:ident),+) => {
829        $(
830            impl<'arena> WithDirectives<'arena> for $for_type<'arena> {
831                #[inline]
832                fn directives(&self) -> &Directives<'arena> {
833                    &self.directives
834                }
835            }
836        )+
837    };
838}
839
840with_directives!(
841    Field,
842    FragmentSpread,
843    InlineFragment,
844    OperationDefinition,
845    FragmentDefinition,
846    VariableDefinition
847);
848
849impl<'arena> WithDirectives<'arena> for Selection<'arena> {
850    /// Helper method to get all Directives for a given selection directly.
851    ///
852    /// Any selection AST node may carry Directives, so when those are checked
853    /// it's unnecessary to first match the type of selection.
854    fn directives(&self) -> &Directives<'arena> {
855        match self {
856            Selection::Field(field) => &field.directives,
857            Selection::FragmentSpread(spread) => &spread.directives,
858            Selection::InlineFragment(fragment) => &fragment.directives,
859        }
860    }
861}
862
863impl<'arena> WithDirectives<'arena> for Definition<'arena> {
864    /// Helper method to get all Directives for a given definition directly.
865    ///
866    /// Any Definition AST node may carry Directives, so when those are checked
867    /// it's unnecessary to first match the type of Definition.
868    #[inline]
869    fn directives(&self) -> &Directives<'arena> {
870        match self {
871            Definition::Operation(operation) => &operation.directives,
872            Definition::Fragment(fragment) => &fragment.directives,
873        }
874    }
875}
876
877/// Trait implemented by all AST nodes that can be skipped via standard skip/include directives.
878pub trait Skippable<'arena>: WithDirectives<'arena> {
879    /// Resolves @include and @skip directives to a flag on whether an AST node must be
880    /// skipped during execution.
881    ///
882    /// [Reference](https://spec.graphql.org/October2021/#sec--skip)
883    #[inline]
884    fn should_include(&'arena self, variables: Option<&Variables<'arena>>) -> bool {
885        for directive in self.directives().children.iter() {
886            if directive.name != "skip" && directive.name != "include" {
887                continue;
888            }
889
890            let if_arg = directive
891                .arguments
892                .children
893                .iter()
894                .find(|arg| arg.name == "if")
895                .map(|arg| arg.value.is_truthy(variables));
896
897            if let Some(if_arg) = if_arg {
898                return (directive.name == "include") == if_arg;
899            }
900        }
901
902        true
903    }
904}
905
906impl<'arena> Skippable<'arena> for Selection<'arena> {}
907impl<'arena> Skippable<'arena> for Field<'arena> {}
908impl<'arena> Skippable<'arena> for InlineFragment<'arena> {}
909impl<'arena> Skippable<'arena> for FragmentSpread<'arena> {}
910
911#[cfg(test)]
912mod tests {
913    use super::{ASTContext, Document};
914    use crate::ast::{ParseNode, PrintNode};
915
916    #[test]
917    fn operation_no_operations() {
918        let ctx = ASTContext::new();
919        let ast = Document::parse(&ctx, r#"fragment Foo on Query { hello }"#).unwrap();
920        assert_eq!(
921            ast.operation(Some("queryName")).unwrap_err().message,
922            "Operation with name queryName does not exist"
923        );
924        assert_eq!(
925            ast.operation(None).unwrap_err().message,
926            "Document does not contain any operations"
927        );
928    }
929
930    #[test]
931    fn operation_one_operation() {
932        let ctx = ASTContext::new();
933        let ast = Document::parse(&ctx, r#"query queryName { hello }"#).unwrap();
934        assert_eq!(
935            ast.operation(Some("queryName")).unwrap().print(),
936            "query queryName {\n  hello\n}"
937        );
938        assert_eq!(
939            ast.operation(None).unwrap().print(),
940            "query queryName {\n  hello\n}"
941        );
942    }
943
944    #[test]
945    fn operation_one_operation_anonymous() {
946        let ctx = ASTContext::new();
947        let ast = Document::parse(&ctx, r#"{ hello }"#).unwrap();
948        assert_eq!(
949            ast.operation(Some("queryName")).unwrap_err().message,
950            "Operation with name queryName does not exist"
951        );
952        assert_eq!(ast.operation(None).unwrap().print(), "{\n  hello\n}");
953    }
954
955    #[test]
956    fn operation_two_operations() {
957        let ctx = ASTContext::new();
958        let ast = Document::parse(
959            &ctx,
960            r#"query queryName { hello } query otherName { world }"#,
961        )
962        .unwrap();
963        assert_eq!(
964            ast.operation(Some("queryName")).unwrap().print(),
965            "query queryName {\n  hello\n}"
966        );
967        assert_eq!(
968            ast.operation(Some("otherName")).unwrap().print(),
969            "query otherName {\n  world\n}"
970        );
971        assert_eq!(
972            ast.operation(Some("badName")).unwrap_err().message,
973            "Operation with name badName does not exist"
974        );
975        assert_eq!(
976            ast.operation(None).unwrap_err().message,
977            "Document contains more than one operation, missing operation name"
978        );
979    }
980
981    #[test]
982    fn operation_two_operations_one_anonymous() {
983        let ctx = ASTContext::new();
984        let ast = Document::parse(&ctx, r#"{ hello } query otherName { world }"#).unwrap();
985        assert_eq!(
986            ast.operation(Some("queryName")).unwrap_err().message,
987            "Operation with name queryName does not exist"
988        );
989        assert_eq!(
990            ast.operation(Some("otherName")).unwrap().print(),
991            "query otherName {\n  world\n}"
992        );
993        assert_eq!(
994            ast.operation(None).unwrap_err().message,
995            "Document contains more than one operation, missing operation name"
996        );
997    }
998}