zf

zenflows testing
git clone https://s.sonu.ch/~srfsh/zf.git
Log | Files | Refs | Submodules | README | LICENSE

analysis.ex (4815B)


      1 defmodule Absinthe.Phase.Document.Complexity.Analysis do
      2   @moduledoc false
      3 
      4   # Analyses document complexity.
      5 
      6   alias Absinthe.{Blueprint, Phase, Complexity, Type}
      7 
      8   use Absinthe.Phase
      9 
     10   @default_complexity 1
     11 
     12   @doc """
     13   Run complexity analysis.
     14   """
     15   @spec run(Blueprint.t(), Keyword.t()) :: Phase.result_t()
     16   def run(input, options \\ []) do
     17     if Keyword.get(options, :analyze_complexity, false) do
     18       do_run(input, options)
     19     else
     20       {:ok, input}
     21     end
     22   end
     23 
     24   defp do_run(input, options) do
     25     info = info_boilerplate(input, options)
     26     fragments = process_fragments(input, info)
     27     fun = &handle_node(&1, info, fragments)
     28     {:ok, Blueprint.postwalk(input, fun)}
     29   end
     30 
     31   defp process_fragments(input, info) do
     32     Enum.reduce(input.fragments, %{}, fn fragment, processed ->
     33       fun = &handle_node(&1, info, processed)
     34       fragment = Blueprint.postwalk(fragment, fun)
     35       Map.put(processed, fragment.name, fragment)
     36     end)
     37   end
     38 
     39   def handle_node(%Blueprint.Document.Fragment.Spread{name: name} = node, _info, fragments) do
     40     fragment = Map.fetch!(fragments, name)
     41     %{node | complexity: fragment.complexity}
     42   end
     43 
     44   def handle_node(
     45         %Blueprint.Document.Fragment.Named{selections: fields} = node,
     46         _info,
     47         _fragments
     48       ) do
     49     %{node | complexity: sum_complexity(fields)}
     50   end
     51 
     52   def handle_node(
     53         %Blueprint.Document.Fragment.Inline{selections: fields} = node,
     54         _info,
     55         _fragments
     56       ) do
     57     %{node | complexity: sum_complexity(fields)}
     58   end
     59 
     60   def handle_node(
     61         %Blueprint.Document.Field{
     62           complexity: nil,
     63           selections: fields,
     64           argument_data: args,
     65           schema_node: schema_node
     66         } = node,
     67         info,
     68         _fragments
     69       ) do
     70     # NOTE:
     71     # This really should be more nuanced. If this particular field's schema node
     72     # is a union type, right now the complexity of:
     73     # thisField {
     74     #   ... User { a b c}
     75     #   ... Dog { x y z }
     76     # }
     77     # would be the complexity of `|a, b, c, x, y, z|` despite the fact that it is
     78     # impossible for `a, b, c` to also happen with `x, y, z`
     79     #
     80     # However, if this schema node is an interface type things get complicated quickly.
     81     # You would have to evaluate the complexity for every possible type which can get
     82     # pretty unwieldy. For now, simple types it is.
     83     child_complexity = sum_complexity(fields)
     84 
     85     schema_node = %{
     86       schema_node
     87       | complexity: Type.function(schema_node, :complexity)
     88     }
     89 
     90     case field_complexity(schema_node, args, child_complexity, info, node) do
     91       complexity when is_integer(complexity) and complexity >= 0 ->
     92         %{node | complexity: complexity}
     93 
     94       other ->
     95         raise Absinthe.AnalysisError, field_value_error(node, other)
     96     end
     97   end
     98 
     99   def handle_node(%Blueprint.Document.Operation{complexity: nil, selections: fields} = node, _, _) do
    100     %{node | complexity: sum_complexity(fields)}
    101   end
    102 
    103   def handle_node(node, _, _) do
    104     node
    105   end
    106 
    107   defp field_complexity(%{complexity: nil}, _, child_complexity, _, _) do
    108     @default_complexity + child_complexity
    109   end
    110 
    111   defp field_complexity(%{complexity: complexity}, arg, child_complexity, _, _)
    112        when is_function(complexity, 2) do
    113     complexity.(arg, child_complexity)
    114   end
    115 
    116   defp field_complexity(%{complexity: complexity}, arg, child_complexity, info, node)
    117        when is_function(complexity, 3) do
    118     info = struct(Complexity, Map.put(info, :definition, node))
    119     complexity.(arg, child_complexity, info)
    120   end
    121 
    122   defp field_complexity(%{complexity: {mod, fun}}, arg, child_complexity, info, node) do
    123     info = struct(Complexity, Map.put(info, :definition, node))
    124     apply(mod, fun, [arg, child_complexity, info])
    125   end
    126 
    127   defp field_complexity(%{complexity: complexity}, _, _, _, _) do
    128     complexity
    129   end
    130 
    131   defp field_value_error(field, value) do
    132     """
    133     Invalid value returned from complexity analyzer.
    134 
    135     Analyzing field:
    136 
    137       #{field.name}
    138 
    139     Defined at:
    140 
    141       #{field.schema_node.__reference__.location.file}:#{
    142       field.schema_node.__reference__.location.line
    143     }
    144 
    145     Got value:
    146 
    147         #{inspect(value)}
    148 
    149     The complexity value must be a non negative integer.
    150     """
    151   end
    152 
    153   defp sum_complexity(fields) do
    154     Enum.reduce(fields, 0, &sum_complexity/2)
    155   end
    156 
    157   defp sum_complexity(%{complexity: complexity}, acc) when is_nil(complexity) do
    158     @default_complexity + acc
    159   end
    160 
    161   defp sum_complexity(%{complexity: complexity}, acc) when is_integer(complexity) do
    162     complexity + acc
    163   end
    164 
    165   # Execution context data that's common to all fields
    166   defp info_boilerplate(bp_root, options) do
    167     %{
    168       context: options[:context] || %{},
    169       root_value: options[:root_value] || %{},
    170       schema: bp_root.schema
    171     }
    172   end
    173 end