zf

zenflows testing
git clone https://s.sonu.ch/~srfsh/zf.git
Log | Files | Refs | Submodules | README | LICENSE

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