Module: OpticsAgent::Normalization::Query

Includes:
GraphQL::Language
Included in:
Reporting::Query
Defined in:
lib/optics-agent/normalization/query.rb

Instance Method Summary collapse

Instance Method Details

#normalize(query_string) ⇒ Object

query is a query string



9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
# File 'lib/optics-agent/normalization/query.rb', line 9

def normalize(query_string)
  document = GraphQL.parse(query_string)

  # to store results
  output = ''
  used_fragment_names = []
  current = {}
  visitor = Visitor.new(document)

  stack = []
  Nodes.constants.each do |constant|
    mod = Nodes.const_get(constant)
    next unless mod.is_a? Module

    visitor[mod].enter << -> (_, _) do
      stack.unshift(
        arguments: [],
        directives: [],
        selections: []
      )
    end

    visitor[mod].leave << -> (_, _) { current = stack.shift }
  end

  visitor[Nodes::Argument].leave << -> (node, parent) do
    stack[0][:arguments] << "#{node.name}:#{genericize_type(node.value)}"
  end

  visitor[Nodes::Directive].leave << -> (node, parent) do
    id = "@#{node.name}"
    arguments = current[:arguments]
    unless arguments.empty?
      id << "(#{arguments.sort.join(', ')})"
    end
    stack[0][:directives] << id
  end

  visitor[Nodes::Field].leave << -> (node, parent) do
    id = node.name
    arguments = current[:arguments]
    unless arguments.empty?
      id << "(#{arguments.sort.join(', ')})"
    end
    directives = current[:directives]
    unless directives.empty?
      id << " #{directives.sort.join(' ')}"
    end
    selections = current[:selections]
    unless selections.empty?
      id << ' ' + block(selections)
    end

    stack[0][:selections] << id
  end

  visitor[Nodes::InlineFragment].leave << -> (node, parent) do
    selections = current[:selections]
    stack[0][:selections] << "... on #{node.type.name} #{block(selections)}"
  end

  visitor[Nodes::FragmentSpread].leave << -> (node, parent) do
    used_fragment_names << node.name
    stack[0][:selections] << "...#{node.name}"
  end

  visitor[Nodes::OperationDefinition].leave << -> (node, parent) do
    # no need to walk this, I don't think anything else can have vars
    vars = nil
    unless node.variables.empty?
      variable_strs = node.variables.sort_by(&:name).map do |variable|
        "$#{variable.name}:#{format_argument_type(variable.type)}"
      end
      vars = "(#{variable_strs.join(',')})"
    end

    query_content = block(current[:selections])
    if (node.name || vars || node.operation_type != 'query')
      parts = [node.operation_type]
      parts << "#{node.name}#{vars}" if (node.name || vars)
      parts << query_content
      output << parts.join(' ')
    else
      output << query_content
    end
  end

  visitor[Nodes::FragmentDefinition].leave << -> (node, parent) do
    selections = current[:selections]
    if (used_fragment_names.include?(node.name))
      output << " fragment #{node.name} on #{node.type.name} " \
        + block(selections)
    end
  end

  visitor.visit
  output
end