zf

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

fields_on_correct_type.ex (7631B)


      1 defmodule Absinthe.Phase.Document.Validation.FieldsOnCorrectType do
      2   @moduledoc false
      3 
      4   # Validates document to ensure that all fields are provided on the correct type.
      5 
      6   alias Absinthe.{Blueprint, Phase, Phase.Document.Validation.Utils, Schema, Type}
      7 
      8   use Absinthe.Phase
      9 
     10   @doc """
     11   Run the validation.
     12   """
     13   @spec run(Blueprint.t(), Keyword.t()) :: Phase.result_t()
     14   def run(input, _options \\ []) do
     15     result = Blueprint.prewalk(input, &handle_node(&1, input))
     16     {:ok, result}
     17   end
     18 
     19   @spec handle_node(Blueprint.node_t(), Schema.t()) :: Blueprint.node_t()
     20   defp handle_node(%Blueprint.Document.Operation{schema_node: nil} = node, _) do
     21     error = %Phase.Error{
     22       phase: __MODULE__,
     23       message: "Operation \"#{node.type}\" not supported",
     24       locations: [node.source_location]
     25     }
     26 
     27     node
     28     |> flag_invalid(:unknown_operation)
     29     |> put_error(error)
     30   end
     31 
     32   defp handle_node(
     33          %{selections: selections, schema_node: parent_schema_node} = node,
     34          %{schema: schema} = input
     35        )
     36        when not is_nil(parent_schema_node) do
     37     possible_parent_types = possible_types(parent_schema_node, schema)
     38 
     39     selections =
     40       Enum.map(selections, fn
     41         %Blueprint.Document.Field{schema_node: nil} = field ->
     42           type = named_type(parent_schema_node, schema)
     43 
     44           field
     45           |> flag_invalid(:unknown_field)
     46           |> put_error(
     47             error(
     48               field,
     49               type.name,
     50               suggested_type_names(field.name, type, input),
     51               suggested_field_names(field.name, type, input)
     52             )
     53           )
     54 
     55         %Blueprint.Document.Fragment.Spread{errors: []} = spread ->
     56           fragment = Enum.find(input.fragments, &(&1.name == spread.name))
     57           possible_child_types = possible_types(fragment.schema_node, schema)
     58 
     59           if Enum.any?(possible_child_types, &(&1 in possible_parent_types)) do
     60             spread
     61           else
     62             spread_error(spread, possible_parent_types, possible_child_types, schema)
     63           end
     64 
     65         %Blueprint.Document.Fragment.Inline{} = fragment ->
     66           possible_child_types = possible_types(fragment.schema_node, schema)
     67 
     68           if Enum.any?(possible_child_types, &(&1 in possible_parent_types)) do
     69             fragment
     70           else
     71             spread_error(fragment, possible_parent_types, possible_child_types, schema)
     72           end
     73 
     74         other ->
     75           other
     76       end)
     77 
     78     %{node | selections: selections}
     79   end
     80 
     81   defp handle_node(node, _) do
     82     node
     83   end
     84 
     85   defp idents_to_names(idents, schema) do
     86     for ident <- idents do
     87       Absinthe.Schema.lookup_type(schema, ident).name
     88     end
     89   end
     90 
     91   defp spread_error(spread, parent_types_idents, child_types_idents, schema) do
     92     parent_types = idents_to_names(parent_types_idents, schema)
     93     child_types = idents_to_names(child_types_idents, schema)
     94 
     95     msg = """
     96     Fragment spread has no type overlap with parent.
     97     Parent possible types: #{inspect(parent_types)}
     98     Spread possible types: #{inspect(child_types)}
     99     """
    100 
    101     error = %Phase.Error{
    102       phase: __MODULE__,
    103       message: msg,
    104       locations: [spread.source_location]
    105     }
    106 
    107     spread
    108     |> flag_invalid(:invalid_spread)
    109     |> put_error(error)
    110   end
    111 
    112   defp possible_types(%{type: type}, schema) do
    113     possible_types(type, schema)
    114   end
    115 
    116   defp possible_types(type, schema) do
    117     schema
    118     |> Absinthe.Schema.lookup_type(type)
    119     |> case do
    120       %Type.Object{identifier: identifier} ->
    121         [identifier]
    122 
    123       %Type.Interface{identifier: identifier} ->
    124         schema.__absinthe_interface_implementors__
    125         |> Map.fetch!(identifier)
    126 
    127       %Type.Union{types: types} ->
    128         types
    129 
    130       _ ->
    131         []
    132     end
    133   end
    134 
    135   @spec named_type(Type.t(), Schema.t()) :: Type.named_t()
    136   defp named_type(%Type.Field{} = node, schema) do
    137     Schema.lookup_type(schema, node.type)
    138   end
    139 
    140   defp named_type(%{name: _} = node, _) do
    141     node
    142   end
    143 
    144   # Generate the error for a field
    145   @spec error(Blueprint.node_t(), String.t(), [String.t()], [String.t()]) :: Phase.Error.t()
    146   defp error(field_node, parent_type_name, type_suggestions, field_suggestions) do
    147     %Phase.Error{
    148       phase: __MODULE__,
    149       message:
    150         error_message(field_node.name, parent_type_name, type_suggestions, field_suggestions),
    151       locations: [field_node.source_location]
    152     }
    153   end
    154 
    155   @doc """
    156   Generate an error for a field
    157   """
    158   @spec error_message(String.t(), String.t(), [String.t()], [String.t()]) :: String.t()
    159   def error_message(field_name, type_name, type_suggestions \\ [], field_suggestions \\ [])
    160 
    161   def error_message(field_name, type_name, [], []) do
    162     ~s(Cannot query field "#{field_name}" on type "#{type_name}".)
    163   end
    164 
    165   def error_message(field_name, type_name, [], field_suggestions) do
    166     error_message(field_name, type_name) <>
    167       Utils.MessageSuggestions.suggest_message(field_suggestions)
    168   end
    169 
    170   def error_message(field_name, type_name, type_suggestions, []) do
    171     error_message(field_name, type_name) <>
    172       Utils.MessageSuggestions.suggest_fragment_message(type_suggestions)
    173   end
    174 
    175   def error_message(field_name, type_name, type_suggestions, _) do
    176     error_message(field_name, type_name, type_suggestions)
    177   end
    178 
    179   defp suggested_type_names(external_field_name, type, blueprint) do
    180     internal_field_name =
    181       case blueprint.adapter.to_internal_name(external_field_name, :field) do
    182         nil -> external_field_name
    183         internal_field_name -> internal_field_name
    184       end
    185 
    186     possible_types = find_possible_types(internal_field_name, type, blueprint.schema)
    187 
    188     possible_interfaces =
    189       find_possible_interfaces(internal_field_name, possible_types, blueprint.schema)
    190 
    191     possible_interfaces
    192     |> Enum.map(& &1.name)
    193     |> Enum.concat(Enum.map(possible_types, & &1.name))
    194     |> Enum.sort()
    195   end
    196 
    197   defp suggested_field_names(external_field_name, %{fields: _} = type, blueprint) do
    198     internal_field_name =
    199       case blueprint.adapter.to_internal_name(external_field_name, :field) do
    200         nil -> external_field_name
    201         internal_field_name -> internal_field_name
    202       end
    203 
    204     Map.values(type.fields)
    205     |> Enum.map(& &1.name)
    206     |> Absinthe.Utils.Suggestion.sort_list(internal_field_name)
    207     |> Enum.map(&blueprint.adapter.to_external_name(&1, :field))
    208     |> Enum.sort()
    209   end
    210 
    211   defp suggested_field_names(_, _, _) do
    212     []
    213   end
    214 
    215   defp find_possible_interfaces(field_name, possible_types, schema) do
    216     possible_types
    217     |> types_to_interface_idents
    218     |> Enum.uniq()
    219     |> sort_by_implementation_count(possible_types)
    220     |> Enum.map(&Schema.lookup_type(schema, &1))
    221     |> types_with_field(field_name)
    222   end
    223 
    224   defp sort_by_implementation_count(iface_idents, types) do
    225     Enum.sort_by(iface_idents, fn iface ->
    226       count =
    227         Enum.count(types, fn
    228           %{interfaces: ifaces} ->
    229             Enum.member?(ifaces, iface)
    230 
    231           _ ->
    232             false
    233         end)
    234 
    235       count
    236     end)
    237     |> Enum.reverse()
    238   end
    239 
    240   defp types_to_interface_idents(types) do
    241     Enum.flat_map(types, fn
    242       %{interfaces: ifaces} ->
    243         ifaces
    244 
    245       _ ->
    246         []
    247     end)
    248   end
    249 
    250   defp find_possible_types(field_name, type, schema) do
    251     schema
    252     |> Schema.concrete_types(Type.unwrap(type))
    253     |> types_with_field(field_name)
    254   end
    255 
    256   defp types_with_field(types, field_name) do
    257     Enum.filter(types, &type_with_field?(&1, field_name))
    258   end
    259 
    260   defp type_with_field?(%{fields: fields}, field_name) do
    261     Map.values(fields)
    262     |> Enum.find(&(&1.name == field_name))
    263   end
    264 
    265   defp type_with_field?(_, _) do
    266     false
    267   end
    268 end