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}