introspection.ex (4878B)
1 defmodule Absinthe.Phase.Schema.Introspection do 2 @moduledoc false 3 4 use Absinthe.Phase 5 alias Absinthe.Blueprint 6 7 alias Absinthe.Blueprint.Schema.FieldDefinition 8 alias Absinthe.Blueprint.Schema.InputValueDefinition 9 alias Absinthe.Blueprint.TypeReference.NonNull 10 alias Absinthe.Blueprint.Schema.ObjectTypeDefinition 11 alias Absinthe.Blueprint.Schema.ListTypeDefinition 12 alias Absinthe.Blueprint.Schema.UnionTypeDefinition 13 alias Absinthe.Blueprint.Schema.InterfaceTypeDefinition 14 15 def __absinthe_function__(identifier, :middleware) do 16 [{{Absinthe.Resolution, :call}, resolve_fn(identifier)}] 17 end 18 19 def run(blueprint, _opts) do 20 blueprint = attach_introspection_fields(blueprint) 21 {:ok, blueprint} 22 end 23 24 @doc """ 25 Append the given field or fields to the given type 26 """ 27 def attach_introspection_fields(blueprint = %Blueprint{}) do 28 %{blueprint | schema_definitions: update_schema_defs(blueprint.schema_definitions)} 29 end 30 31 def update_schema_defs(schema_definitions) do 32 for schema_def = %{type_definitions: type_defs} <- schema_definitions do 33 %{schema_def | type_definitions: update_type_defs(type_defs)} 34 end 35 end 36 37 def update_type_defs(type_defs) do 38 for type_def = %struct_type{} <- type_defs do 39 cond do 40 type_def.name in ["RootQueryType", "Query"] -> 41 type_field = field_def(:type) 42 schema_field = field_def(:schema) 43 typename_field = field_def(:typename) 44 %{type_def | fields: [type_field, schema_field, typename_field | type_def.fields]} 45 46 struct_type in [ 47 ObjectTypeDefinition, 48 ListTypeDefinition, 49 UnionTypeDefinition, 50 InterfaceTypeDefinition 51 ] -> 52 typename_field = field_def(:typename) 53 %{type_def | fields: [typename_field | type_def.fields]} 54 55 true -> 56 type_def 57 end 58 end 59 end 60 61 def field_def(:typename) do 62 %FieldDefinition{ 63 name: "__typename", 64 identifier: :__typename, 65 module: __MODULE__, 66 type: :string, 67 description: "The name of the object type currently being queried.", 68 complexity: 0, 69 triggers: %{}, 70 middleware: [ 71 {:ref, __MODULE__, :typename} 72 ], 73 flags: %{reserved_name: true}, 74 __reference__: Absinthe.Schema.Notation.build_reference(__ENV__) 75 } 76 end 77 78 def field_def(:type) do 79 %FieldDefinition{ 80 __reference__: Absinthe.Schema.Notation.build_reference(__ENV__), 81 name: "__type", 82 identifier: :__type, 83 type: :__type, 84 module: __MODULE__, 85 description: "Represents scalars, interfaces, object types, unions, enums in the system", 86 triggers: %{}, 87 arguments: [ 88 %InputValueDefinition{ 89 __reference__: Absinthe.Schema.Notation.build_reference(__ENV__), 90 module: __MODULE__, 91 identifier: :name, 92 name: "name", 93 type: %NonNull{of_type: :string}, 94 description: "The name of the type to introspect" 95 } 96 ], 97 middleware: [ 98 {:ref, __MODULE__, :type} 99 ], 100 flags: %{reserved_name: true} 101 } 102 end 103 104 def field_def(:schema) do 105 %FieldDefinition{ 106 name: "__schema", 107 identifier: :__schema, 108 type: :__schema, 109 module: __MODULE__, 110 description: "Represents the schema", 111 triggers: %{}, 112 middleware: [ 113 {:ref, __MODULE__, :schema} 114 ], 115 flags: %{reserved_name: true}, 116 __reference__: Absinthe.Schema.Notation.build_reference(__ENV__) 117 } 118 end 119 120 def resolve_fn(:schema) do 121 fn _, %{schema: schema} -> 122 {:ok, schema} 123 end 124 end 125 126 def resolve_fn(:type) do 127 fn %{name: name}, %{schema: schema} -> 128 type_def = 129 case Absinthe.Schema.lookup_type(schema, name) do 130 type_def = %{fields: fields} -> 131 %{type_def | fields: filter_fields(fields)} 132 133 type_def -> 134 type_def 135 end 136 137 {:ok, type_def} 138 end 139 end 140 141 def resolve_fn(:typename) do 142 fn 143 _, %{parent_type: %Absinthe.Type.Object{} = type} -> 144 {:ok, type.name} 145 146 _, %{source: source, parent_type: %Absinthe.Type.Interface{} = iface} = env -> 147 case Absinthe.Type.Interface.resolve_type(iface, source, env) do 148 nil -> 149 {:error, "Could not resolve type of concrete " <> iface.name} 150 151 type -> 152 {:ok, type.name} 153 end 154 155 _, %{source: source, parent_type: %Absinthe.Type.Union{} = union} = env -> 156 case Absinthe.Type.Union.resolve_type(union, source, env) do 157 nil -> 158 {:error, "Could not resolve type of concrete " <> union.name} 159 160 type -> 161 {:ok, type.name} 162 end 163 end 164 end 165 166 def filter_fields(fields) do 167 for {key, field = %{name: name}} <- fields, not String.starts_with?(name, "__"), into: %{} do 168 {key, field} 169 end 170 end 171 end