ℹ️ Select 'Choose Exercise', or randomize 'Next Random Exercise' in selected language.

Choose Exercise:
Timer 00:00
WPM --
Score --
Acc --
Correct chars --

GraphQL Query Depth Limiter

GraphQL

Goal -- WPM

Ready
Exercise Algorithm Area
1package main
2
3import (
4 "fmt"
5 "strings"
6)
7
8type GraphQLField struct {
9 Name string
10 Alias string
11 Arguments map[string]interface{}
12 Directives []string
13 SelectionSet []*GraphQLField
14}
15
16func limitQueryDepth(fields []*GraphQLField, maxDepth int, currentDepth int) []*GraphQLField {
17 if currentDepth > maxDepth {
18 return nil // Prune this entire branch
19 }
20
21 limitedFields := []*GraphQLField{}
22
23 for _, field := range fields {
24 // Create a new field to avoid modifying the original AST directly
25 newField := &GraphQLField{
26 Name: field.Name,
27 Alias: field.Alias,
28 Arguments: field.Arguments,
29 Directives: field.Directives,
30 }
31
32 if len(field.SelectionSet) > 0 {
33 // Recursively limit the depth of the selection set
34 limitedSubSelection := limitQueryDepth(field.SelectionSet, maxDepth, currentDepth+1)
35 newField.SelectionSet = limitedSubSelection
36 }
37
38 // Only add the field if its selection set is not nil (meaning it wasn't pruned)
39 // Or if it's a leaf node (no selection set) and within depth limits
40 if newField.SelectionSet != nil || len(field.SelectionSet) == 0 {
41 limitedFields = append(limitedFields, newField)
42 }
43 }
44
45 return limitedFields
46}
47
48func main() {
49 // Example: A complex selection set with varying depths
50 queryFields := []*GraphQLField{
51 {Name: "user", Alias: "u",
52 SelectionSet: []*GraphQLField{
53 {Name: "id"},
54 {Name: "name"},
55 {Name: "posts", SelectionSet: []*GraphQLField{
56 {Name: "id"},
57 {Name: "title"},
58 {Name: "comments", SelectionSet: []*GraphQLField{
59 {Name: "text"},
60 {Name: "author", SelectionSet: []*GraphQLField{
61 {Name: "username"}
62 }}
63 }}
64 }},
65 {Name: "profile", SelectionSet: []*GraphQLField{
66 {Name: "bio"},
67 {Name: "avatarUrl"}
68 }}
69 }}
70 },
71 {Name: "version"}
72 }
73
74 maxDepth := 3
75
76 fmt.Printf("Original Query Structure:\n")
77 printQueryStructure(queryFields, " ")
78
79 limitedFields := limitQueryDepth(queryFields, maxDepth, 1) // Start at depth 1 for root fields
80
81 fmt.Printf("\nQuery Structure with Max Depth %d:\n", maxDepth)
82 printQueryStructure(limitedFields, " ")
83
84 // Example with a smaller max depth
85 maxDepth = 2
86 limitedFields = limitQueryDepth(queryFields, maxDepth, 1)
87 fmt.Printf("\nQuery Structure with Max Depth %d:\n", maxDepth)
88 printQueryStructure(limitedFields, " ")
89}
90
91func printQueryStructure(fields []*GraphQLField, indent string) {
92 if fields == nil {
93 fmt.Printf("%s(Pruned)\n", indent)
94 return
95 }
96
97 for _, field := range fields {
98 aliasPart := ""
99 if field.Alias != "" {
100 aliasPart = field.Alias + ": "
101 }
102 fmt.Printf("%s%s%s", indent, aliasPart, field.Name)
103 if len(field.SelectionSet) > 0 {
104 fmt.Println(" {")
105 printQueryStructure(field.SelectionSet, indent+" ")
106 fmt.Printf("%s}", indent)
107 }
108 fmt.Println()
109 }
110}
Algorithm description viewbox

GraphQL Query Depth Limiter

Algorithm description:

This Go program implements a GraphQL query depth limiter. It traverses a simplified Abstract Syntax Tree (AST) representation of a GraphQL query and prunes any fields that exceed a specified maximum depth. This is a crucial technique for preventing denial-of-service attacks and ensuring predictable performance by controlling query complexity.

Algorithm explanation:

The `limitQueryDepth` function uses recursion to traverse the query's field structure. It takes the current list of fields, the maximum allowed depth, and the current depth as parameters. If the `currentDepth` exceeds `maxDepth`, the function returns `nil`, effectively pruning that branch. Otherwise, it iterates through the fields, creating new field objects to avoid modifying the original AST. For fields with selection sets, it recursively calls `limitQueryDepth` with an incremented `currentDepth`. A field is added to the result only if its pruned selection set is not `nil` or if it's a leaf node within the depth limit. The time complexity is O(N) where N is the total number of fields in the query AST, as each field is visited once. The space complexity is O(D) where D is the maximum depth of the query, due to the recursion stack.

Pseudocode:

function limitQueryDepth(fields, maxDepth, currentDepth):
  if currentDepth > maxDepth:
    return null

  initialize empty list `limitedFields`

  for each `field` in `fields`:
    create a `newField` by copying `field`'s properties (Name, Alias, Arguments, Directives)

    if `field` has a `SelectionSet`:
      recursively call `limitQueryDepth` on `field.SelectionSet` with `maxDepth` and `currentDepth + 1` to get `limitedSubSelection`
      set `newField.SelectionSet` to `limitedSubSelection`

    if `newField.SelectionSet` is not null OR `field` has no `SelectionSet`:
      add `newField` to `limitedFields`

  return `limitedFields`