blueprint.ex (6251B)
1 defmodule Absinthe.Blueprint do 2 @moduledoc """ 3 Represents the graphql document to be executed. 4 5 Please see the code itself for more information on individual blueprint sub 6 modules. 7 """ 8 9 alias __MODULE__ 10 11 defstruct operations: [], 12 directives: [], 13 fragments: [], 14 name: nil, 15 schema_definitions: [], 16 schema: nil, 17 prototype_schema: nil, 18 adapter: nil, 19 initial_phases: [], 20 # Added by phases 21 telemetry: %{}, 22 flags: %{}, 23 errors: [], 24 input: nil, 25 source: nil, 26 execution: %Blueprint.Execution{}, 27 result: %{} 28 29 @type t :: %__MODULE__{ 30 operations: [Blueprint.Document.Operation.t()], 31 schema_definitions: [Blueprint.Schema.SchemaDefinition.t()], 32 directives: [Blueprint.Schema.DirectiveDefinition.t()], 33 name: nil | String.t(), 34 fragments: [Blueprint.Document.Fragment.Named.t()], 35 schema: nil | Absinthe.Schema.t(), 36 prototype_schema: nil | Absinthe.Schema.t(), 37 adapter: nil | Absinthe.Adapter.t(), 38 # Added by phases 39 telemetry: map, 40 errors: [Absinthe.Phase.Error.t()], 41 flags: flags_t, 42 input: nil | Absinthe.Language.Document.t(), 43 source: nil | String.t() | Absinthe.Language.Source.t(), 44 execution: Blueprint.Execution.t(), 45 result: result_t, 46 initial_phases: [Absinthe.Phase.t()] 47 } 48 49 @type result_t :: %{ 50 optional(:data) => term, 51 optional(:errors) => [term], 52 optional(:extensions) => term 53 } 54 55 @type node_t :: 56 t() 57 | Blueprint.Directive.t() 58 | Blueprint.Document.t() 59 | Blueprint.Schema.t() 60 | Blueprint.Input.t() 61 | Blueprint.TypeReference.t() 62 63 @type use_t :: 64 Blueprint.Document.Fragment.Named.Use.t() 65 | Blueprint.Input.Variable.Use.t() 66 67 @type flags_t :: %{atom => module} 68 69 defdelegate prewalk(blueprint, fun), to: Absinthe.Blueprint.Transform 70 defdelegate prewalk(blueprint, acc, fun), to: Absinthe.Blueprint.Transform 71 defdelegate postwalk(blueprint, fun), to: Absinthe.Blueprint.Transform 72 defdelegate postwalk(blueprint, acc, fun), to: Absinthe.Blueprint.Transform 73 74 def find(blueprint, fun) do 75 {_, found} = 76 Blueprint.prewalk(blueprint, nil, fn 77 node, nil -> 78 if fun.(node) do 79 {node, node} 80 else 81 {node, nil} 82 end 83 84 node, found -> 85 # Already found 86 {node, found} 87 end) 88 89 found 90 end 91 92 @doc false 93 # This is largely a debugging tool which replaces `schema_node` struct values 94 # with just the type identifier, rendering the blueprint tree much easier to read 95 def __compress__(blueprint) do 96 prewalk(blueprint, fn 97 %{schema_node: %{identifier: id}} = node -> 98 %{node | schema_node: id} 99 100 node -> 101 node 102 end) 103 end 104 105 @spec fragment(t, String.t()) :: nil | Blueprint.Document.Fragment.Named.t() 106 def fragment(blueprint, name) do 107 Enum.find(blueprint.fragments, &(&1.name == name)) 108 end 109 110 @doc """ 111 Add a flag to a node. 112 """ 113 @spec put_flag(node_t, atom, module) :: node_t 114 def put_flag(node, flag, mod) do 115 update_in(node.flags, &Map.put(&1, flag, mod)) 116 end 117 118 @doc """ 119 Determine whether a flag has been set on a node. 120 """ 121 @spec flagged?(node_t, atom) :: boolean 122 def flagged?(node, flag) do 123 Map.has_key?(node.flags, flag) 124 end 125 126 @doc """ 127 Get the currently selected operation. 128 """ 129 @spec current_operation(t) :: nil | Blueprint.Document.Operation.t() 130 def current_operation(blueprint) do 131 Enum.find(blueprint.operations, &(&1.current == true)) 132 end 133 134 @doc """ 135 Update the current operation. 136 """ 137 @spec update_current(t, (Blueprint.Document.Operation.t() -> Blueprint.Document.Operation.t())) :: 138 t 139 def update_current(blueprint, change) do 140 ops = 141 Enum.map(blueprint.operations, fn 142 %{current: true} = op -> 143 change.(op) 144 145 other -> 146 other 147 end) 148 149 %{blueprint | operations: ops} 150 end 151 152 @doc """ 153 Append the given field or fields to the given type 154 """ 155 def extend_fields(blueprint = %Blueprint{}, ext_blueprint = %Blueprint{}) do 156 ext_types = types_by_name(ext_blueprint) 157 158 schema_defs = 159 for schema_def = %{type_definitions: type_defs} <- blueprint.schema_definitions do 160 type_defs = 161 for type_def <- type_defs do 162 case ext_types[type_def.name] do 163 nil -> 164 type_def 165 166 %{fields: new_fields} -> 167 %{type_def | fields: type_def.fields ++ new_fields} 168 end 169 end 170 171 %{schema_def | type_definitions: type_defs} 172 end 173 174 %{blueprint | schema_definitions: schema_defs} 175 end 176 177 def extend_fields(blueprint, ext_blueprint) when is_atom(ext_blueprint) do 178 extend_fields(blueprint, ext_blueprint.__absinthe_blueprint__()) 179 end 180 181 def add_field(blueprint = %Blueprint{}, type_def_name, new_field) do 182 schema_defs = 183 for schema_def = %{type_definitions: type_defs} <- blueprint.schema_definitions do 184 type_defs = 185 for type_def <- type_defs do 186 if type_def.name == type_def_name do 187 %{type_def | fields: type_def.fields ++ List.wrap(new_field)} 188 else 189 type_def 190 end 191 end 192 193 %{schema_def | type_definitions: type_defs} 194 end 195 196 %{blueprint | schema_definitions: schema_defs} 197 end 198 199 def find_field(%{fields: fields}, name) do 200 Enum.find(fields, fn %{name: field_name} -> field_name == name end) 201 end 202 203 @doc """ 204 Index the types by their name 205 """ 206 def types_by_name(blueprint = %Blueprint{}) do 207 for %{type_definitions: type_defs} <- blueprint.schema_definitions, 208 type_def <- type_defs, 209 into: %{} do 210 {type_def.name, type_def} 211 end 212 end 213 214 def types_by_name(module) when is_atom(module) do 215 types_by_name(module.__absinthe_blueprint__()) 216 end 217 218 defimpl Inspect do 219 defdelegate inspect(term, options), 220 to: Absinthe.Schema.Notation.SDL.Render 221 end 222 end