resolution.ex (12781B)
1 defmodule Absinthe.Phase.Document.Execution.Resolution do 2 @moduledoc false 3 4 # Runs resolution functions in a blueprint. 5 # 6 # Blueprint results are placed under `blueprint.result.execution`. This is 7 # because the results form basically a new tree from the original blueprint. 8 9 alias Absinthe.{Blueprint, Type, Phase} 10 alias Blueprint.{Result, Execution} 11 12 alias Absinthe.Phase 13 use Absinthe.Phase 14 15 @spec run(Blueprint.t(), Keyword.t()) :: Phase.result_t() 16 def run(bp_root, options \\ []) do 17 case Blueprint.current_operation(bp_root) do 18 nil -> {:ok, bp_root} 19 op -> resolve_current(bp_root, op, options) 20 end 21 end 22 23 defp resolve_current(bp_root, operation, options) do 24 execution = perform_resolution(bp_root, operation, options) 25 26 blueprint = %{bp_root | execution: execution} 27 28 if Keyword.get(options, :plugin_callbacks, true) do 29 bp_root.schema.plugins() 30 |> Absinthe.Plugin.pipeline(execution) 31 |> case do 32 [] -> 33 {:ok, blueprint} 34 35 pipeline -> 36 {:insert, blueprint, pipeline} 37 end 38 else 39 {:ok, blueprint} 40 end 41 end 42 43 defp perform_resolution(bp_root, operation, options) do 44 exec = Execution.get(bp_root, operation) 45 46 plugins = bp_root.schema.plugins() 47 run_callbacks? = Keyword.get(options, :plugin_callbacks, true) 48 49 exec = plugins |> run_callbacks(:before_resolution, exec, run_callbacks?) 50 51 common = 52 Map.take(exec, [:adapter, :context, :acc, :root_value, :schema, :fragments, :fields_cache]) 53 54 res = 55 %Absinthe.Resolution{ 56 path: nil, 57 source: nil, 58 parent_type: nil, 59 middleware: nil, 60 definition: nil, 61 arguments: nil 62 } 63 |> Map.merge(common) 64 65 {result, res} = 66 exec.result 67 |> walk_result(operation, operation.schema_node, res, [operation]) 68 |> propagate_null_trimming 69 70 exec = update_persisted_fields(exec, res) 71 72 exec = plugins |> run_callbacks(:after_resolution, exec, run_callbacks?) 73 74 %{exec | result: result} 75 end 76 77 defp run_callbacks(plugins, callback, acc, true) do 78 Enum.reduce(plugins, acc, &apply(&1, callback, [&2])) 79 end 80 81 defp run_callbacks(_, _, acc, _), do: acc 82 83 @doc """ 84 This function walks through any existing results. If no results are found at a 85 given node, it will call the requisite function to expand and build those results 86 """ 87 def walk_result(%{fields: nil} = result, bp_node, _schema_type, res, path) do 88 {fields, res} = resolve_fields(bp_node, res, result.root_value, path) 89 {%{result | fields: fields}, res} 90 end 91 92 def walk_result(%{fields: fields} = result, bp_node, schema_type, res, path) do 93 {fields, res} = walk_results(fields, bp_node, schema_type, res, [0 | path], []) 94 95 {%{result | fields: fields}, res} 96 end 97 98 def walk_result(%Result.Leaf{} = result, _, _, res, _) do 99 {result, res} 100 end 101 102 def walk_result(%{values: values} = result, bp_node, schema_type, res, path) do 103 {values, res} = walk_results(values, bp_node, schema_type, res, [0 | path], []) 104 {%{result | values: values}, res} 105 end 106 107 def walk_result(%Absinthe.Resolution{} = old_res, _bp_node, _schema_type, res, _path) do 108 res = update_persisted_fields(old_res, res) 109 do_resolve_field(res, res.source, res.path) 110 end 111 112 # walk list results 113 defp walk_results([value | values], bp_node, inner_type, res, [i | sub_path] = path, acc) do 114 {result, res} = walk_result(value, bp_node, inner_type, res, path) 115 walk_results(values, bp_node, inner_type, res, [i + 1 | sub_path], [result | acc]) 116 end 117 118 defp walk_results([], _, _, res, _, acc), do: {:lists.reverse(acc), res} 119 120 defp resolve_fields(parent, res, source, path) do 121 # parent is the parent field, we need to get the return type of that field 122 # that return type could be an interface or union, so let's make it concrete 123 parent 124 |> get_return_type 125 |> get_concrete_type(source, res) 126 |> case do 127 nil -> 128 {[], res} 129 130 parent_type -> 131 {fields, fields_cache} = 132 Absinthe.Resolution.Projector.project( 133 parent.selections, 134 parent_type, 135 path, 136 res.fields_cache, 137 res 138 ) 139 140 res = %{res | fields_cache: fields_cache} 141 142 do_resolve_fields(fields, res, source, parent_type, path, []) 143 end 144 end 145 146 defp get_return_type(%{schema_node: %Type.Field{type: type}}) do 147 Type.unwrap(type) 148 end 149 150 defp get_return_type(%{schema_node: schema_node}) do 151 Type.unwrap(schema_node) 152 end 153 154 defp get_return_type(type), do: type 155 156 defp get_concrete_type(%Type.Union{} = parent_type, source, res) do 157 Type.Union.resolve_type(parent_type, source, res) 158 end 159 160 defp get_concrete_type(%Type.Interface{} = parent_type, source, res) do 161 Type.Interface.resolve_type(parent_type, source, res) 162 end 163 164 defp get_concrete_type(parent_type, _source, _res) do 165 parent_type 166 end 167 168 defp do_resolve_fields([field | fields], res, source, parent_type, path, acc) do 169 field = %{field | parent_type: parent_type} 170 {result, res} = resolve_field(field, res, source, parent_type, [field | path]) 171 do_resolve_fields(fields, res, source, parent_type, path, [result | acc]) 172 end 173 174 defp do_resolve_fields([], res, _, _, _, acc), do: {:lists.reverse(acc), res} 175 176 def resolve_field(field, res, source, parent_type, path) do 177 res 178 |> build_resolution_struct(field, source, parent_type, path) 179 |> do_resolve_field(source, path) 180 end 181 182 # bp_field needs to have a concrete schema node, AKA no unions or interfaces 183 defp do_resolve_field(res, source, path) do 184 res 185 |> reduce_resolution 186 |> case do 187 %{state: :resolved} = res -> 188 build_result(res, source, path) 189 190 %{state: :suspended} = res -> 191 {res, res} 192 193 final_res -> 194 raise """ 195 Should have halted or suspended middleware 196 Started with: #{inspect(res)} 197 Ended with: #{inspect(final_res)} 198 """ 199 end 200 end 201 202 defp update_persisted_fields(dest, %{acc: acc, context: context, fields_cache: cache}) do 203 %{dest | acc: acc, context: context, fields_cache: cache} 204 end 205 206 defp build_resolution_struct( 207 res, 208 %{argument_data: args, schema_node: %{middleware: middleware}} = bp_field, 209 source, 210 parent_type, 211 path 212 ) do 213 %{ 214 res 215 | path: path, 216 state: :unresolved, 217 value: nil, 218 errors: [], 219 source: source, 220 parent_type: parent_type, 221 middleware: middleware, 222 definition: bp_field, 223 arguments: args 224 } 225 end 226 227 defp reduce_resolution(%{middleware: []} = res), do: res 228 229 defp reduce_resolution(%{middleware: [middleware | remaining_middleware]} = res) do 230 case call_middleware(middleware, %{res | middleware: remaining_middleware}) do 231 %{state: :suspended} = res -> 232 res 233 234 res -> 235 reduce_resolution(res) 236 end 237 end 238 239 defp call_middleware({{mod, fun}, opts}, res) do 240 apply(mod, fun, [res, opts]) 241 end 242 243 defp call_middleware({mod, opts}, res) do 244 apply(mod, :call, [res, opts]) 245 end 246 247 defp call_middleware(mod, res) when is_atom(mod) do 248 apply(mod, :call, [res, []]) 249 end 250 251 defp call_middleware(fun, res) when is_function(fun, 2) do 252 fun.(res, []) 253 end 254 255 defp build_result(res, source, path) do 256 %{ 257 value: value, 258 definition: bp_field, 259 extensions: extensions, 260 schema: schema, 261 errors: errors 262 } = res 263 264 full_type = Type.expand(bp_field.schema_node.type, schema) 265 266 bp_field = put_in(bp_field.schema_node.type, full_type) 267 268 # if there are any errors, the value is always nil 269 value = 270 case errors do 271 [] -> value 272 _ -> nil 273 end 274 275 errors = maybe_add_non_null_error(errors, value, full_type) 276 277 value 278 |> to_result(bp_field, full_type, extensions) 279 |> add_errors(Enum.reverse(errors), &put_result_error_value(&1, &2, bp_field, source, path)) 280 |> walk_result(bp_field, full_type, res, path) 281 |> propagate_null_trimming 282 end 283 284 defp maybe_add_non_null_error([], nil, %Type.NonNull{}) do 285 ["Cannot return null for non-nullable field"] 286 end 287 288 defp maybe_add_non_null_error(errors, _, _) do 289 errors 290 end 291 292 defp propagate_null_trimming({%{values: values} = node, res}) do 293 values = Enum.map(values, &do_propagate_null_trimming/1) 294 node = %{node | values: values} 295 {do_propagate_null_trimming(node), res} 296 end 297 298 defp propagate_null_trimming({node, res}) do 299 {do_propagate_null_trimming(node), res} 300 end 301 302 defp do_propagate_null_trimming(node) do 303 if bad_child = find_bad_child(node) do 304 bp_field = node.emitter 305 306 full_type = 307 with %{type: type} <- bp_field.schema_node do 308 type 309 end 310 311 nil 312 |> to_result(bp_field, full_type, node.extensions) 313 |> Map.put(:errors, bad_child.errors) 314 315 # ^ We don't have to worry about clobbering the current node's errors because, 316 # if it had any errors, it wouldn't have any children and we wouldn't be 317 # here anyway. 318 else 319 node 320 end 321 end 322 323 defp find_bad_child(%{fields: fields}) do 324 Enum.find(fields, &non_null_violation?/1) 325 end 326 327 defp find_bad_child(%{values: values}) do 328 Enum.find(values, &non_null_list_violation?/1) 329 end 330 331 defp find_bad_child(_) do 332 false 333 end 334 335 # FIXME: Not super happy with this lookup process 336 defp non_null_violation?(%{value: nil, emitter: %{schema_node: %{type: %Type.NonNull{}}}}) do 337 true 338 end 339 340 defp non_null_violation?(_) do 341 false 342 end 343 344 # FIXME: Not super happy with this lookup process. 345 # Also it would be nice if we could use the same function as above. 346 defp non_null_list_violation?(%{ 347 value: nil, 348 emitter: %{schema_node: %{type: %Type.List{of_type: %Type.NonNull{}}}} 349 }) do 350 true 351 end 352 353 defp non_null_list_violation?(%{ 354 value: nil, 355 emitter: %{ 356 schema_node: %{type: %Type.NonNull{of_type: %Type.List{of_type: %Type.NonNull{}}}} 357 } 358 }) do 359 true 360 end 361 362 defp non_null_list_violation?(_) do 363 false 364 end 365 366 defp add_errors(result, errors, fun) do 367 Enum.reduce(errors, result, fun) 368 end 369 370 defp put_result_error_value(error_value, result, bp_field, source, path) do 371 case split_error_value(error_value) do 372 {[], _} -> 373 raise Absinthe.Resolution.result_error(error_value, bp_field, source) 374 375 {[message: message, path: error_path], extra} -> 376 put_error( 377 result, 378 error(bp_field, message, Enum.reverse(error_path) ++ path, Map.new(extra)) 379 ) 380 381 {[message: message], extra} -> 382 put_error(result, error(bp_field, message, path, Map.new(extra))) 383 end 384 end 385 386 defp split_error_value(error_value) when is_list(error_value) or is_map(error_value) do 387 Keyword.split(Enum.to_list(error_value), [:message, :path]) 388 end 389 390 defp split_error_value(error_value) when is_binary(error_value) do 391 {[message: error_value], []} 392 end 393 394 defp split_error_value(error_value) do 395 {[message: to_string(error_value)], []} 396 end 397 398 defp to_result(nil, blueprint, _, extensions) do 399 %Result.Leaf{emitter: blueprint, value: nil, extensions: extensions} 400 end 401 402 defp to_result(root_value, blueprint, %Type.NonNull{of_type: inner_type}, extensions) do 403 to_result(root_value, blueprint, inner_type, extensions) 404 end 405 406 defp to_result(root_value, blueprint, %Type.Object{}, extensions) do 407 %Result.Object{root_value: root_value, emitter: blueprint, extensions: extensions} 408 end 409 410 defp to_result(root_value, blueprint, %Type.Interface{}, extensions) do 411 %Result.Object{root_value: root_value, emitter: blueprint, extensions: extensions} 412 end 413 414 defp to_result(root_value, blueprint, %Type.Union{}, extensions) do 415 %Result.Object{root_value: root_value, emitter: blueprint, extensions: extensions} 416 end 417 418 defp to_result(root_value, blueprint, %Type.List{of_type: inner_type}, extensions) do 419 values = 420 root_value 421 |> List.wrap() 422 |> Enum.map(&to_result(&1, blueprint, inner_type, extensions)) 423 424 %Result.List{values: values, emitter: blueprint, extensions: extensions} 425 end 426 427 defp to_result(root_value, blueprint, %Type.Scalar{}, extensions) do 428 %Result.Leaf{ 429 emitter: blueprint, 430 value: root_value, 431 extensions: extensions 432 } 433 end 434 435 defp to_result(root_value, blueprint, %Type.Enum{}, extensions) do 436 %Result.Leaf{ 437 emitter: blueprint, 438 value: root_value, 439 extensions: extensions 440 } 441 end 442 443 def error(node, message, path, extra) do 444 %Phase.Error{ 445 phase: __MODULE__, 446 message: message, 447 locations: [node.source_location], 448 path: Absinthe.Resolution.path(%{path: path}), 449 extra: extra 450 } 451 end 452 end