type_imports.ex (2776B)
1 defmodule Absinthe.Phase.Schema.TypeImports do 2 @moduledoc false 3 4 use Absinthe.Phase 5 alias Absinthe.Blueprint 6 7 alias Absinthe.Blueprint.Schema 8 9 def run(blueprint, _opts) do 10 blueprint = Blueprint.prewalk(blueprint, &handle_imports/1) 11 {:ok, blueprint} 12 end 13 14 @default_imports [ 15 {Absinthe.Type.BuiltIns.Scalars, []}, 16 {Absinthe.Type.BuiltIns.Directives, []}, 17 {Absinthe.Type.BuiltIns.Introspection, []} 18 ] 19 def handle_imports(%Schema.SchemaDefinition{} = schema) do 20 {types, schema} = 21 do_imports(@default_imports ++ schema.imports, schema.type_definitions, schema) 22 23 # special casing the import of the built in directives 24 [builtins] = Absinthe.Type.BuiltIns.Directives.__absinthe_blueprint__().schema_definitions 25 directives = schema.directive_definitions ++ builtins.directive_definitions 26 {:halt, %{schema | type_definitions: types, directive_definitions: directives}} 27 end 28 29 def handle_imports(node), do: node 30 31 defp do_imports([], types, schema) do 32 {types, schema} 33 end 34 35 defp do_imports([{module, opts} | rest], acc, schema) do 36 case ensure_compiled(module) do 37 {:module, module} -> 38 [other_def] = module.__absinthe_blueprint__.schema_definitions 39 40 rejections = 41 MapSet.new([:query, :mutation, :subscription] ++ Keyword.get(opts, :except, [])) 42 43 types = Enum.reject(other_def.type_definitions, &(&1.identifier in rejections)) 44 45 types = 46 case Keyword.fetch(opts, :only) do 47 {:ok, selections} -> 48 Enum.filter(types, &(&1.identifier in selections)) 49 50 _ -> 51 types 52 end 53 54 do_imports(other_def.imports ++ rest, types ++ acc, schema) 55 56 {:error, reason} -> 57 do_imports(rest, acc, schema |> put_error(error(module, reason))) 58 end 59 end 60 61 # Elixir v1.12 includes a Code.ensure_compiled!/1 that tells 62 # the compiler it should only continue if the module is available. 63 # This gives the Elixir compiler more information to address 64 # deadlocks. 65 # TODO: Remove the else clause once we require Elixir v1.12+. 66 @compile {:no_warn_undefined, {Code, :ensure_compiled!, 1}} 67 @dialyzer {:nowarn_function, [ensure_compiled: 1]} 68 defp ensure_compiled(module) do 69 if function_exported?(Code, :ensure_compiled!, 1) do 70 {:module, Code.ensure_compiled!(module)} 71 else 72 Code.ensure_compiled(module) 73 end 74 end 75 76 # Generate an error when loading module fails 77 @spec error(module :: module(), error :: :embedded | :badfile | :nofile | :on_load_failure) :: 78 Absinthe.Phase.Error.t() 79 defp error(module, reason) do 80 %Absinthe.Phase.Error{ 81 message: "Could not load module `#{module}`. It returned reason: `#{reason}`.", 82 phase: __MODULE__ 83 } 84 end 85 end