apply_declaration.ex (3663B)
1 defmodule Absinthe.Phase.Schema.ApplyDeclaration do 2 @moduledoc false 3 4 use Absinthe.Phase 5 alias Absinthe.Blueprint 6 7 @type operation :: :query | :mutation | :subscription 8 9 @type root_mappings :: %{operation() => Blueprint.TypeReference.Name.t()} 10 11 def run(blueprint, _opts) do 12 blueprint = process(blueprint) 13 {:ok, blueprint} 14 end 15 16 # Apply schema declaration to each schema definition 17 @spec process(blueprint :: Blueprint.t()) :: Blueprint.t() 18 defp process(blueprint = %Blueprint{}) do 19 %{ 20 blueprint 21 | schema_definitions: Enum.map(blueprint.schema_definitions, &process_schema_definition/1) 22 } 23 end 24 25 # Strip the schema declaration out of the schema's type definitions and apply it 26 @spec process_schema_definition(schema_definition :: Blueprint.Schema.SchemaDefinition.t()) :: 27 Blueprint.Schema.SchemaDefinition.t() 28 defp process_schema_definition(schema_definition) do 29 {declarations, type_defs} = 30 Enum.split_with( 31 schema_definition.type_definitions, 32 &match?(%Blueprint.Schema.SchemaDeclaration{}, &1) 33 ) 34 35 # Remove declaration 36 schema_definition = %{schema_definition | type_definitions: type_defs} 37 38 case declarations do 39 [declaration] -> 40 root_mappings = 41 declaration 42 |> extract_root_mappings 43 44 %{ 45 schema_definition 46 | type_definitions: 47 Enum.map(schema_definition.type_definitions, &maybe_mark_root(&1, root_mappings)), 48 schema_declaration: declaration 49 } 50 51 [] -> 52 schema_definition 53 54 [_first | extra_declarations] -> 55 extra_declarations 56 |> Enum.reduce(schema_definition, fn declaration, acc -> 57 acc 58 |> put_error(error(declaration)) 59 end) 60 end 61 end 62 63 # Generate an error for extraneous schema declarations 64 @spec error(declaration :: Blueprint.Schema.SchemaDeclaration.t()) :: Absinthe.Phase.Error.t() 65 defp error(declaration) do 66 %Absinthe.Phase.Error{ 67 message: 68 "More than one schema declaration found. Only one instance of `schema' should be present in SDL.", 69 locations: [declaration.__reference__.location], 70 phase: __MODULE__ 71 } 72 end 73 74 # Extract the declared root type names 75 @spec extract_root_mappings(declaration :: Blueprint.Schema.SchemaDeclaration.t()) :: 76 root_mappings() 77 defp extract_root_mappings(declaration) do 78 for field_def <- declaration.field_definitions, 79 field_def.identifier in ~w(query mutation subscription)a, 80 into: %{} do 81 {field_def.identifier, field_def.type} 82 end 83 end 84 85 # If the type definition is declared as a root type, set the identifier appropriately 86 @spec maybe_mark_root(type_def :: Blueprint.Schema.t(), root_mappings :: root_mappings()) :: 87 Blueprint.Schema.t() 88 defp maybe_mark_root(%Blueprint.Schema.ObjectTypeDefinition{} = type_def, root_mappings) do 89 case operation_root_identifier(type_def, root_mappings) do 90 nil -> 91 type_def 92 93 identifier -> 94 %{type_def | identifier: identifier} 95 end 96 end 97 98 defp maybe_mark_root(type_def, _root_mappings), do: type_def 99 100 # Determine which, if any, root identifier should be applied to an object type definition 101 @spec operation_root_identifier( 102 type_def :: Blueprint.Schema.ObjectTypeDefinition.t(), 103 root_mappings :: root_mappings() 104 ) :: nil | operation() 105 defp operation_root_identifier(type_def, root_mappings) do 106 match_name = type_def.name 107 108 Enum.find_value(root_mappings, fn 109 {ident, %{name: ^match_name}} -> 110 ident 111 112 _ -> 113 false 114 end) 115 end 116 end