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