zf

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

no_fragment_cycles.ex (4232B)


      1 defmodule Absinthe.Phase.Document.Validation.NoFragmentCycles do
      2   @moduledoc false
      3 
      4   # Ensure that document doesn't have any fragment cycles that could
      5   # result in a loop during execution.
      6   #
      7   # Note that if this phase fails, an error should immediately be given to
      8   # the user.
      9 
     10   alias Absinthe.{Blueprint, Phase}
     11 
     12   use Absinthe.Phase
     13 
     14   @doc """
     15   Run the validation.
     16   """
     17   @spec run(Blueprint.t(), Keyword.t()) :: Phase.result_t()
     18   def run(input, options \\ []) do
     19     do_run(input, Map.new(options))
     20   end
     21 
     22   @spec do_run(Blueprint.t(), %{validation_result_phase: Phase.t()}) :: Phase.result_t()
     23   def do_run(input, %{validation_result_phase: abort_phase}) do
     24     {fragments, error_count} = check(input.fragments)
     25     result = %{input | fragments: fragments}
     26 
     27     if error_count > 0 do
     28       {:jump, result, abort_phase}
     29     else
     30       {:ok, result}
     31     end
     32   end
     33 
     34   # Check a list of fragments for cycles
     35   @spec check([Blueprint.Document.Fragment.Named.t()]) ::
     36           {[Blueprint.Document.Fragment.Named.t()], integer}
     37   defp check(fragments) do
     38     graph = :digraph.new([:cyclic])
     39 
     40     try do
     41       with {fragments, 0} <- check(fragments, graph) do
     42         fragments = Map.new(fragments, &{&1.name, &1})
     43 
     44         fragments =
     45           graph
     46           |> :digraph_utils.topsort()
     47           |> Enum.reverse()
     48           |> Enum.map(&Map.get(fragments, &1))
     49           |> Enum.reject(&is_nil/1)
     50 
     51         {fragments, 0}
     52       end
     53     after
     54       :digraph.delete(graph)
     55     end
     56   end
     57 
     58   @spec check([Blueprint.Document.Fragment.Named.t()], :digraph.graph()) ::
     59           {[Blueprint.Document.Fragment.Named.t()], integer}
     60   defp check(fragments, graph) do
     61     Enum.each(fragments, fn node -> Blueprint.prewalk(node, &vertex(&1, graph)) end)
     62 
     63     {modified, error_count} =
     64       Enum.reduce(fragments, {[], 0}, fn fragment, {processed, error_count} ->
     65         errors_to_add = cycle_errors(fragment, :digraph.get_cycle(graph, fragment.name))
     66         fragment_with_errors = update_in(fragment.errors, &(errors_to_add ++ &1))
     67         {[fragment_with_errors | processed], error_count + length(errors_to_add)}
     68       end)
     69 
     70     {modified, error_count}
     71   end
     72 
     73   # Add a vertex modeling a fragment
     74   @spec vertex(Blueprint.Document.Fragment.Named.t(), :digraph.graph()) ::
     75           Blueprint.Document.Fragment.Named.t()
     76   defp vertex(%Blueprint.Document.Fragment.Named{} = fragment, graph) do
     77     :digraph.add_vertex(graph, fragment.name)
     78 
     79     Blueprint.prewalk(fragment, fn
     80       %Blueprint.Document.Fragment.Spread{} = spread ->
     81         edge(fragment, spread, graph)
     82         spread
     83 
     84       node ->
     85         node
     86     end)
     87 
     88     fragment
     89   end
     90 
     91   defp vertex(fragment, _graph) do
     92     fragment
     93   end
     94 
     95   # Add an edge, modeling the relationship between two fragments
     96   @spec edge(
     97           Blueprint.Document.Fragment.Named.t(),
     98           Blueprint.Document.Fragment.Spread.t(),
     99           :digraph.graph()
    100         ) :: true
    101   defp edge(fragment, spread, graph) do
    102     :digraph.add_vertex(graph, spread.name)
    103     :digraph.add_edge(graph, fragment.name, spread.name)
    104     true
    105   end
    106 
    107   # Generate an error for a cyclic reference
    108   @spec cycle_errors(Blueprint.Document.Fragment.Named.t(), false | [String.t()]) :: [
    109           Phase.Error.t()
    110         ]
    111   defp cycle_errors(_, false) do
    112     []
    113   end
    114 
    115   defp cycle_errors(fragment, cycles) do
    116     [cycle_error(fragment, error_message(fragment.name, cycles))]
    117   end
    118 
    119   @doc """
    120   Generate the error message.
    121   """
    122   @spec error_message(String.t(), [String.t()]) :: String.t()
    123   def error_message(fragment_name, [fragment_name]) do
    124     ~s(Cannot spread fragment "#{fragment_name}" within itself.)
    125   end
    126 
    127   def error_message(fragment_name, [_fragment_name | cycles]) do
    128     deps = Enum.map(cycles, &~s("#{&1}")) |> Enum.join(", ")
    129     ~s(Cannot spread fragment "#{fragment_name}" within itself via #{deps}.)
    130   end
    131 
    132   # Generate the error for a fragment cycle
    133   @spec cycle_error(Blueprint.Document.Fragment.Named.t(), String.t()) :: Phase.Error.t()
    134   defp cycle_error(fragment, message) do
    135     %Phase.Error{
    136       message: message,
    137       phase: __MODULE__,
    138       locations: [
    139         %{line: fragment.source_location.line, column: fragment.source_location.column}
    140       ]
    141     }
    142   end
    143 end