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}