schema.ex (8545B)
1 defmodule Absinthe.Phase.Schema do 2 @moduledoc false 3 4 # Populate all schema nodes and the adapter for the blueprint tree. If the 5 # blueprint tree is a _schema_ tree, this schema is the meta schema (source of 6 # SDL directives, etc). 7 # 8 # Note that no validation occurs in this phase. 9 10 use Absinthe.Phase 11 12 alias Absinthe.{Blueprint, Type, Schema} 13 14 # The approach here is pretty simple. 15 # We start at the top blueprint node and set the appropriate schema node on operations 16 # directives and so forth. 17 # 18 # Then, as `prewalk` walks down the tree we hit a node. If that node has a schema_node 19 # set by its parent, we walk to its children and set the schema node on those children. 20 # We do not need to walk any further because `prewalk` will do that for us. 21 # 22 # Thus at each node we need only concern ourselves with immediate children. 23 @spec run(Blueprint.t(), Keyword.t()) :: {:ok, Blueprint.t()} 24 def run(input, options \\ []) do 25 {input, schema} = apply_settings(input, Map.new(options)) 26 27 result = 28 input 29 |> update_context(schema) 30 |> Blueprint.prewalk(&handle_node(&1, schema, input.adapter)) 31 32 {:ok, result} 33 end 34 35 # Set schema and adapter settings on the blueprint appropriate to whether we're 36 # applying a normal schema for a document or a prototype schema used to define 37 # a schema. 38 defp apply_settings(input, %{prototype_schema: schema} = options) do 39 adapter = Map.get(options, :adapter, Absinthe.Adapter.LanguageConventions) 40 {%{input | prototype_schema: schema, adapter: adapter}, schema} 41 end 42 43 defp apply_settings(input, options) do 44 adapter = Map.get(options, :adapter, Absinthe.Adapter.LanguageConventions) 45 {%{input | schema: options.schema, adapter: adapter}, options.schema} 46 end 47 48 defp update_context(input, nil), do: input 49 50 defp update_context(input, schema) do 51 context = schema.context(input.execution.context) 52 put_in(input.execution.context, context) 53 end 54 55 defp handle_node(%Blueprint{} = node, schema, adapter) do 56 set_children(node, schema, adapter) 57 end 58 59 defp handle_node(%Absinthe.Blueprint.Document.VariableDefinition{} = node, _, _) do 60 {:halt, node} 61 end 62 63 defp handle_node(node, schema, adapter) do 64 set_children(node, schema, adapter) 65 end 66 67 defp set_children(parent, schema, adapter) do 68 Blueprint.prewalk(parent, fn 69 ^parent -> parent 70 %Absinthe.Blueprint.Input.Variable{} = child -> {:halt, child} 71 child -> {:halt, set_schema_node(child, parent, schema, adapter)} 72 end) 73 end 74 75 # Do note, the `parent` arg is the parent blueprint node, not the parent's schema node. 76 defp set_schema_node( 77 %Blueprint.Document.Fragment.Inline{type_condition: %{name: type_name} = condition} = 78 node, 79 _parent, 80 schema, 81 _adapter 82 ) do 83 schema_node = Absinthe.Schema.lookup_type(schema, type_name) 84 %{node | schema_node: schema_node, type_condition: %{condition | schema_node: schema_node}} 85 end 86 87 defp set_schema_node(%Blueprint.Directive{name: name} = node, _parent, schema, adapter) do 88 %{node | schema_node: find_schema_directive(name, schema, adapter)} 89 end 90 91 defp set_schema_node( 92 %Blueprint.Document.Operation{type: op_type} = node, 93 _parent, 94 schema, 95 _adapter 96 ) do 97 %{node | schema_node: Absinthe.Schema.lookup_type(schema, op_type)} 98 end 99 100 defp set_schema_node( 101 %Blueprint.Document.Fragment.Named{type_condition: %{name: type_name} = condition} = 102 node, 103 _parent, 104 schema, 105 _adapter 106 ) do 107 schema_node = Absinthe.Schema.lookup_type(schema, type_name) 108 %{node | schema_node: schema_node, type_condition: %{condition | schema_node: schema_node}} 109 end 110 111 defp set_schema_node( 112 %Blueprint.Document.VariableDefinition{type: type_reference} = node, 113 _parent, 114 schema, 115 _adapter 116 ) do 117 wrapped = 118 type_reference 119 |> type_reference_to_type(schema) 120 121 %{node | schema_node: wrapped} 122 end 123 124 defp set_schema_node(node, %{schema_node: nil}, _, _) do 125 # if we don't know the parent schema node, and we aren't one of the earlier nodes, 126 # then we can't know our schema node. 127 node 128 end 129 130 defp set_schema_node( 131 %Blueprint.Document.Fragment.Inline{type_condition: nil} = node, 132 parent, 133 schema, 134 adapter 135 ) do 136 type = 137 case parent.schema_node do 138 %{type: type} -> type 139 other -> other 140 end 141 |> Type.expand(schema) 142 |> Type.unwrap() 143 144 set_schema_node( 145 %{node | type_condition: %Blueprint.TypeReference.Name{name: type.name, schema_node: type}}, 146 parent, 147 schema, 148 adapter 149 ) 150 end 151 152 defp set_schema_node(%Blueprint.Document.Field{} = node, parent, schema, adapter) do 153 %{node | schema_node: find_schema_field(parent.schema_node, node.name, schema, adapter)} 154 end 155 156 defp set_schema_node(%Blueprint.Input.Argument{name: name} = node, parent, _schema, adapter) do 157 schema_node = find_schema_argument(parent.schema_node, name, adapter) 158 %{node | schema_node: schema_node} 159 end 160 161 defp set_schema_node(%Blueprint.Document.Fragment.Spread{} = node, _, _, _) do 162 node 163 end 164 165 defp set_schema_node(%Blueprint.Input.Field{} = node, parent, schema, adapter) do 166 case node.name do 167 "__" <> _ -> 168 %{node | schema_node: nil} 169 170 name -> 171 %{node | schema_node: find_schema_field(parent.schema_node, name, schema, adapter)} 172 end 173 end 174 175 defp set_schema_node(%Blueprint.Input.List{} = node, parent, _schema, _adapter) do 176 case Type.unwrap_non_null(parent.schema_node) do 177 %{of_type: internal_type} -> 178 %{node | schema_node: internal_type} 179 180 _ -> 181 node 182 end 183 end 184 185 defp set_schema_node(%Blueprint.Input.Value{} = node, parent, schema, _) do 186 case parent.schema_node do 187 %Type.Argument{type: type} -> 188 %{node | schema_node: type |> Type.expand(schema)} 189 190 %Absinthe.Type.Field{type: type} -> 191 %{node | schema_node: type |> Type.expand(schema)} 192 193 type -> 194 %{node | schema_node: type |> Type.expand(schema)} 195 end 196 end 197 198 defp set_schema_node(%{schema_node: nil} = node, %Blueprint.Input.Value{} = parent, _schema, _) do 199 %{node | schema_node: parent.schema_node} 200 end 201 202 defp set_schema_node(node, _, _, _) do 203 node 204 end 205 206 # Given a schema field or directive, lookup a child argument definition 207 @spec find_schema_argument( 208 nil | Type.Field.t() | Type.Argument.t(), 209 String.t(), 210 Absinthe.Adapter.t() 211 ) :: nil | Type.Argument.t() 212 defp find_schema_argument(%{args: arguments}, name, adapter) do 213 internal_name = adapter.to_internal_name(name, :argument) 214 215 arguments 216 |> Map.values() 217 |> Enum.find(&match?(%{name: ^internal_name}, &1)) 218 end 219 220 # Given a name, lookup a schema directive 221 @spec find_schema_directive(String.t(), Absinthe.Schema.t(), Absinthe.Adapter.t()) :: 222 nil | Type.Directive.t() 223 defp find_schema_directive(name, schema, adapter) do 224 internal_name = adapter.to_internal_name(name, :directive) 225 schema.__absinthe_directive__(internal_name) 226 end 227 228 # Given a schema type, lookup a child field definition 229 @spec find_schema_field(nil | Type.t(), String.t(), Absinthe.Schema.t(), Absinthe.Adapter.t()) :: 230 nil | Type.Field.t() 231 232 defp find_schema_field(%{of_type: type}, name, schema, adapter) do 233 find_schema_field(type, name, schema, adapter) 234 end 235 236 defp find_schema_field(%{fields: fields}, name, _schema, adapter) do 237 internal_name = adapter.to_internal_name(name, :field) 238 239 fields 240 |> Map.values() 241 |> Enum.find(&match?(%{name: ^internal_name}, &1)) 242 end 243 244 defp find_schema_field(%Type.Field{type: maybe_wrapped_type}, name, schema, adapter) do 245 type = 246 Type.unwrap(maybe_wrapped_type) 247 |> schema.__absinthe_lookup__ 248 249 find_schema_field(type, name, schema, adapter) 250 end 251 252 defp find_schema_field(_, _, _, _) do 253 nil 254 end 255 256 @type_mapping %{ 257 Blueprint.TypeReference.List => Type.List, 258 Blueprint.TypeReference.NonNull => Type.NonNull 259 } 260 defp type_reference_to_type(%Blueprint.TypeReference.Name{name: name}, schema) do 261 Schema.lookup_type(schema, name) 262 end 263 264 for {blueprint_type, core_type} <- @type_mapping do 265 defp type_reference_to_type(%unquote(blueprint_type){} = node, schema) do 266 inner = type_reference_to_type(node.of_type, schema) 267 %unquote(core_type){of_type: inner} 268 end 269 end 270 end