zf

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

projector.ex (4769B)


      1 defmodule Absinthe.Resolution.Projector do
      2   @moduledoc false
      3 
      4   alias Absinthe.{Blueprint, Type}
      5 
      6   @doc """
      7   Project one layer down from where we are right now.
      8 
      9   Projection amounts to collecting the next set of fields to operate on, based on
     10   the current field. This is a non trivial operation because you have to handle
     11   the various type conditions that come along with fragments / inline fragments,
     12   field merging, and other wonderful stuff like that.
     13   """
     14   def project(selections, %{identifier: parent_ident} = parent_type, path, cache, exec) do
     15     path =
     16       for %{parent_type: %{identifier: i}, name: name, alias: alias} <- path do
     17         {i, alias || name}
     18       end
     19 
     20     key = [parent_ident | path]
     21 
     22     case Map.fetch(cache, key) do
     23       {:ok, fields} ->
     24         {fields, cache}
     25 
     26       _ ->
     27         fields =
     28           selections
     29           |> collect(parent_type, exec)
     30           |> rectify_order
     31 
     32         {fields, Map.put(cache, key, fields)}
     33     end
     34   end
     35 
     36   defp response_key(%{alias: nil, name: name}), do: name
     37   defp response_key(%{alias: alias}), do: alias
     38   defp response_key(%{name: name}), do: name
     39 
     40   defp collect(selections, parent_type, %{fragments: fragments, schema: schema}) do
     41     {acc, _index} = do_collect(selections, fragments, parent_type, schema, 0, %{})
     42     acc
     43   end
     44 
     45   defp do_collect([], _, _, _, index, acc), do: {acc, index}
     46 
     47   defp do_collect([selection | selections], fragments, parent_type, schema, index, acc) do
     48     case selection do
     49       %{flags: %{skip: _}} ->
     50         do_collect(selections, fragments, parent_type, schema, index, acc)
     51 
     52       %Blueprint.Document.Field{} = field ->
     53         field = update_schema_node(field, parent_type)
     54         key = response_key(field)
     55 
     56         acc =
     57           Map.update(acc, key, {index, [field]}, fn {existing_index, fields} ->
     58             {existing_index, [field | fields]}
     59           end)
     60 
     61         do_collect(selections, fragments, parent_type, schema, index + 1, acc)
     62 
     63       %Blueprint.Document.Fragment.Inline{
     64         type_condition: %{schema_node: condition},
     65         selections: inner_selections
     66       } ->
     67         {acc, index} =
     68           conditionally_collect(
     69             condition,
     70             inner_selections,
     71             fragments,
     72             parent_type,
     73             schema,
     74             index,
     75             acc
     76           )
     77 
     78         do_collect(selections, fragments, parent_type, schema, index, acc)
     79 
     80       %Blueprint.Document.Fragment.Spread{name: name} ->
     81         %{type_condition: condition, selections: inner_selections} = Map.fetch!(fragments, name)
     82 
     83         {acc, index} =
     84           conditionally_collect(
     85             condition,
     86             inner_selections,
     87             fragments,
     88             parent_type,
     89             schema,
     90             index,
     91             acc
     92           )
     93 
     94         do_collect(selections, fragments, parent_type, schema, index, acc)
     95     end
     96   end
     97 
     98   defp rectify_order(grouped_fields) do
     99     grouped_fields
    100     |> Enum.sort(fn {_, {i1, _}}, {_, {i2, _}} ->
    101       i1 <= i2
    102     end)
    103     |> Enum.map(fn
    104       {_k, {_index, [field]}} ->
    105         field
    106 
    107       {_k, {_index, [%{selections: selections} = field | rest]}} ->
    108         %{field | selections: flatten(rest, selections)}
    109     end)
    110   end
    111 
    112   defp flatten([], acc), do: acc
    113 
    114   defp flatten([%{selections: selections} | fields], acc) do
    115     flatten(fields, selections ++ acc)
    116   end
    117 
    118   defp conditionally_collect(condition, selections, fragments, parent_type, schema, index, acc) do
    119     condition
    120     |> normalize_condition(schema)
    121     |> passes_type_condition?(parent_type)
    122     |> case do
    123       true -> do_collect(selections, fragments, parent_type, schema, index, acc)
    124       false -> {acc, index}
    125     end
    126   end
    127 
    128   # necessary when the field in question is on an abstract type.
    129   defp update_schema_node(%{name: "__" <> _} = field, _) do
    130     field
    131   end
    132 
    133   defp update_schema_node(%{schema_node: %{identifier: identifier}} = field, %{
    134          fields: concrete_fields
    135        }) do
    136     %{field | schema_node: :maps.get(identifier, concrete_fields)}
    137   end
    138 
    139   defp normalize_condition(%{schema_node: condition}, schema) do
    140     normalize_condition(condition, schema)
    141   end
    142 
    143   defp normalize_condition(condition, schema) do
    144     case Type.unwrap(condition) do
    145       %{} = condition -> condition
    146       value -> Absinthe.Schema.lookup_type(schema, value)
    147     end
    148   end
    149 
    150   defp passes_type_condition?(%Type.Object{name: name}, %Type.Object{name: name}) do
    151     true
    152   end
    153 
    154   defp passes_type_condition?(%Type.Interface{} = condition, %Type.Object{} = type) do
    155     Type.Interface.member?(condition, type)
    156   end
    157 
    158   defp passes_type_condition?(%Type.Union{} = condition, %Type.Object{} = type) do
    159     Type.Union.member?(condition, type)
    160   end
    161 
    162   defp passes_type_condition?(_, _) do
    163     false
    164   end
    165 end