Packages
absinthe
1.7.9
1.11.0
1.10.2
1.10.1
1.10.0
1.9.1
1.9.0
1.8.0
1.7.11
1.7.10
1.7.9
1.7.8
1.7.7
1.7.6
1.7.5
1.7.4
1.7.3
1.7.2
1.7.1
1.7.0
1.6.8
1.6.7
retired
1.6.6
1.6.5
1.6.4
1.6.3
1.6.2
1.6.1
1.6.0
1.6.0-rc.1
1.6.0-rc.0
1.5.5
1.5.4
1.5.3
1.5.2
1.5.1
1.5.0
1.5.0-rc.5
1.5.0-rc.4
1.5.0-rc.3
1.5.0-rc.2
1.5.0-rc.1
1.5.0-rc.0
1.5.0-beta.2
1.5.0-beta.1
1.5.0-beta.0
1.5.0-alpha.4
1.5.0-alpha.3
1.5.0-alpha.2
1.5.0-alpha.1
1.5.0-alpha.0
1.4.16
1.4.15
1.4.14
1.4.13
1.4.12
1.4.11
1.4.10
1.4.9
1.4.8
retired
1.4.7
1.4.6
1.4.5
1.4.4
1.4.3
1.4.2
1.4.1
1.4.0
1.4.0-rc.3
1.4.0-rc.2
1.4.0-rc.1
1.4.0-rc.0
1.4.0-beta.5
1.4.0-beta.4
1.4.0-beta.3
1.4.0-beta.2
1.4.0-beta.1
1.3.2
1.3.1
1.3.0
1.3.0-rc.0
1.3.0-beta.2
1.3.0-beta.1
1.3.0-beta.0
1.2.6
1.2.5
1.2.4
1.2.3
1.2.2
1.2.1
1.2.0
1.2.0-rc.0
1.2.0-beta.0
1.2.0-alpha0
1.2.0-alpha.2
1.2.0-alpha.1
1.1.11
1.1.10
1.1.9
1.1.8
1.1.7
1.1.6
1.1.5
1.1.4
1.1.3
1.1.2
1.1.1
1.1.0
1.0.0
0.5.2
0.5.1
0.5.0
0.4.6
0.4.5
0.4.4
0.4.3
0.4.2
0.4.1
0.4.0
0.2.3
0.2.2
0.2.1
0.1.0
GraphQL for Elixir
Security advisory:
This version has known vulnerabilities.
View advisories
Current section
Files
Jump to
Current section
Files
lib/absinthe/phase/document/complexity/analysis.ex
defmodule Absinthe.Phase.Document.Complexity.Analysis do
@moduledoc false
# Analyses document complexity.
alias Absinthe.{Blueprint, Phase, Complexity, Type}
use Absinthe.Phase
@default_complexity 1
@doc """
Run complexity analysis.
"""
@spec run(Blueprint.t(), Keyword.t()) :: Phase.result_t()
def run(input, options \\ []) do
if Keyword.get(options, :analyze_complexity, false) do
do_run(input, options)
else
{:ok, input}
end
end
defp do_run(input, options) do
info = info_boilerplate(input, options)
fragments = process_fragments(input, info)
fun = &handle_node(&1, info, fragments)
{:ok, Blueprint.postwalk(input, fun)}
end
defp process_fragments(input, info) do
Enum.reduce(input.fragments, %{}, fn fragment, processed ->
fun = &handle_node(&1, info, processed)
fragment = Blueprint.postwalk(fragment, fun)
Map.put(processed, fragment.name, fragment)
end)
end
def handle_node(%Blueprint.Document.Fragment.Spread{name: name} = node, _info, fragments) do
fragment = Map.fetch!(fragments, name)
%{node | complexity: fragment.complexity}
end
def handle_node(
%Blueprint.Document.Fragment.Named{selections: fields} = node,
_info,
_fragments
) do
%{node | complexity: sum_complexity(fields)}
end
def handle_node(
%Blueprint.Document.Fragment.Inline{selections: fields} = node,
_info,
_fragments
) do
%{node | complexity: sum_complexity(fields)}
end
def handle_node(
%Blueprint.Document.Field{
complexity: nil,
selections: fields,
argument_data: args,
schema_node: schema_node
} = node,
info,
_fragments
) do
# NOTE:
# This really should be more nuanced. If this particular field's schema node
# is a union type, right now the complexity of:
# thisField {
# ... User { a b c}
# ... Dog { x y z }
# }
# would be the complexity of `|a, b, c, x, y, z|` despite the fact that it is
# impossible for `a, b, c` to also happen with `x, y, z`
#
# However, if this schema node is an interface type things get complicated quickly.
# You would have to evaluate the complexity for every possible type which can get
# pretty unwieldy. For now, simple types it is.
child_complexity = sum_complexity(fields)
schema_node = %{
schema_node
| complexity: Type.function(schema_node, :complexity)
}
case field_complexity(schema_node, args, child_complexity, info, node) do
complexity when is_integer(complexity) and complexity >= 0 ->
%{node | complexity: complexity}
other ->
raise Absinthe.AnalysisError, field_value_error(node, other)
end
end
def handle_node(%Blueprint.Document.Operation{complexity: nil, selections: fields} = node, _, _) do
%{node | complexity: sum_complexity(fields)}
end
def handle_node(node, _, _) do
node
end
defp field_complexity(%{complexity: nil}, _, child_complexity, _, _) do
@default_complexity + child_complexity
end
defp field_complexity(%{complexity: complexity}, arg, child_complexity, _, _)
when is_function(complexity, 2) do
complexity.(arg, child_complexity)
end
defp field_complexity(%{complexity: complexity}, arg, child_complexity, info, node)
when is_function(complexity, 3) do
info = struct(Complexity, Map.put(info, :definition, node))
complexity.(arg, child_complexity, info)
end
defp field_complexity(%{complexity: {mod, fun}}, arg, child_complexity, info, node) do
info = struct(Complexity, Map.put(info, :definition, node))
apply(mod, fun, [arg, child_complexity, info])
end
defp field_complexity(%{complexity: complexity}, _, _, _, _) do
complexity
end
defp field_value_error(field, value) do
"""
Invalid value returned from complexity analyzer.
Analyzing field:
#{field.name}
Defined at:
#{field.schema_node.__reference__.location.file}:#{field.schema_node.__reference__.location.line}
Got value:
#{inspect(value)}
The complexity value must be a non negative integer.
"""
end
defp sum_complexity(fields) do
Enum.reduce(fields, 0, &sum_complexity/2)
end
defp sum_complexity(%{complexity: complexity}, acc) when is_nil(complexity) do
@default_complexity + acc
end
defp sum_complexity(%{complexity: complexity}, acc) when is_integer(complexity) do
complexity + acc
end
# Execution context data that's common to all fields
defp info_boilerplate(bp_root, options) do
%{
context: options[:context] || %{},
root_value: options[:root_value] || %{},
schema: bp_root.schema
}
end
end