Skip to main content

graphql_query/ast/
ast.rs

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