no_circular_field_imports.ex (2283B)
1 defmodule Absinthe.Phase.Schema.Validation.NoCircularFieldImports do 2 @moduledoc false 3 4 use Absinthe.Phase 5 alias Absinthe.Blueprint 6 alias Absinthe.Blueprint.Schema 7 8 def run(blueprint, _opts) do 9 blueprint = Blueprint.prewalk(blueprint, &validate_schema/1) 10 {:ok, blueprint} 11 end 12 13 def validate_schema(%Schema.SchemaDefinition{type_definitions: types} = schema) do 14 {:halt, %{schema | type_definitions: sort_and_validate_types(types)}} 15 end 16 17 def validate_schema(node), do: node 18 19 def sort_and_validate_types(types) do 20 graph = :digraph.new([:cyclic]) 21 22 try do 23 _ = build_import_graph(types, graph) 24 25 {types, cycles?} = 26 Enum.reduce(types, {%{}, false}, fn type, {types, cycles?} -> 27 if cycle = :digraph.get_cycle(graph, type.identifier) do 28 type = type |> put_error(error(type, cycle)) 29 {Map.put(types, type.identifier, type), true} 30 else 31 {Map.put(types, type.identifier, type), cycles?} 32 end 33 end) 34 35 if cycles? do 36 Map.values(types) 37 else 38 graph 39 |> :digraph_utils.topsort() 40 |> Enum.reverse() 41 |> Enum.flat_map(fn identifier -> 42 case Map.fetch(types, identifier) do 43 {:ok, type} -> [type] 44 _ -> [] 45 end 46 end) 47 end 48 after 49 :digraph.delete(graph) 50 end 51 end 52 53 defp error(type, deps) do 54 %Absinthe.Phase.Error{ 55 message: 56 String.trim(""" 57 Field Import Cycle Error 58 59 Field Import in object `#{type.identifier}' `import_fields(#{inspect(type.imports)}) forms a cycle via: (#{ 60 inspect(deps) 61 }) 62 """), 63 locations: [type.__reference__.location], 64 phase: __MODULE__, 65 extra: type.identifier 66 } 67 end 68 69 defp build_import_graph(types, graph) do 70 Enum.each(types, &add_to_graph(&1, graph)) 71 end 72 73 defp add_to_graph(type, graph) do 74 :digraph.add_vertex(graph, type.identifier) 75 76 with %{imports: imports} <- type do 77 for {ident, _} <- imports do 78 :digraph.add_vertex(graph, ident) 79 80 case :digraph.add_edge(graph, type.identifier, ident) do 81 {:error, _} -> 82 raise "edge failed" 83 84 _ -> 85 :ok 86 end 87 end 88 end 89 end 90 end