zf

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

resolution.ex (11230B)


      1 defmodule Absinthe.Resolution do
      2   @moduledoc """
      3   Information about the current resolution. It is created by adding field specific
      4   information to the more general `%Absinthe.Blueprint.Execution{}` struct.
      5 
      6   In many ways like the `%Conn{}` from `Plug`, the `%Absinthe.Resolution{}` is the
      7   piece of information that passed along from middleware to middleware as part of
      8   resolution.
      9 
     10   ## Contents
     11   - `:adapter` - The adapter used for any name conversions.
     12   - `:definition` - The Blueprint definition for this field.
     13   - `:context` - The context passed to `Absinthe.run`.
     14   - `:root_value` - The root value passed to `Absinthe.run`, if any.
     15   - `:parent_type` - The parent type for the field.
     16   - `:private` - Operates similarly to the `:private` key on a `%Plug.Conn{}`
     17     and is a place for libraries (and similar) to store their information.
     18   - `:schema` - The current schema.
     19   - `:source` - The resolved parent object; source of this field.
     20 
     21   When a `%Resolution{}` is accessed via middleware, you may want to update the
     22   context (e.g. to cache a dataloader instance or the result of an ecto query).
     23   Updating the context can be done simply by using the map updating syntax (or
     24   `Map.put/3`):
     25 
     26   ```elixir
     27   %{resolution | context: new_context}
     28   # OR
     29   Map.put(resolution, :context, new_context)
     30   ```
     31 
     32   To access the schema type for this field, see the `definition.schema_node`.
     33   """
     34 
     35   @typedoc """
     36   The arguments that are passed from the schema. (e.g. id of the record to be
     37   fetched)
     38   """
     39   @type arguments :: %{optional(atom) => any}
     40   @type source :: any
     41 
     42   @type t :: %__MODULE__{
     43           value: term,
     44           errors: [term],
     45           adapter: Absinthe.Adapter.t(),
     46           context: map,
     47           root_value: any,
     48           schema: Absinthe.Schema.t(),
     49           definition: Absinthe.Blueprint.node_t(),
     50           parent_type: Absinthe.Type.t(),
     51           source: source,
     52           state: field_state,
     53           acc: %{any => any},
     54           extensions: %{any => any},
     55           arguments: arguments,
     56           fragments: [Absinthe.Blueprint.Document.Fragment.Named.t()]
     57         }
     58 
     59   defstruct [
     60     :value,
     61     :adapter,
     62     :context,
     63     :parent_type,
     64     :root_value,
     65     :definition,
     66     :schema,
     67     :source,
     68     errors: [],
     69     middleware: [],
     70     acc: %{},
     71     arguments: %{},
     72     extensions: %{},
     73     private: %{},
     74     path: [],
     75     state: :unresolved,
     76     fragments: [],
     77     fields_cache: %{}
     78   ]
     79 
     80   def resolver_spec(fun) do
     81     {{__MODULE__, :call}, fun}
     82   end
     83 
     84   @type field_state :: :unresolved | :resolved | :suspended
     85 
     86   @doc """
     87   Get the child fields under the current field.
     88 
     89   See `project/2` for details.
     90   """
     91   def project(info) do
     92     case info.definition.schema_node.type do
     93       %Absinthe.Type.Interface{} ->
     94         raise need_concrete_type_error()
     95 
     96       %Absinthe.Type.Union{} ->
     97         raise need_concrete_type_error()
     98 
     99       schema_node ->
    100         project(info, schema_node)
    101     end
    102   end
    103 
    104   @doc """
    105   Get the current path.
    106 
    107   Each `Absinthe.Resolution` struct holds the current result path as a list of
    108   blueprint nodes and indices. Usually however you don't need the full AST list
    109   and instead just want the path that will eventually end up in the result.
    110 
    111   For that, use this function.
    112 
    113   ## Examples
    114   Given some query:
    115   ```graphql
    116   {users { email }}
    117   ```
    118 
    119   If you called this function inside a resolver on the users email field it
    120   returns a value like:
    121 
    122   ```elixir
    123   resolve fn _, _, resolution ->
    124     Absinthe.Resolution.path(resolution) #=> ["users", 5, "email"]
    125   end
    126   ```
    127 
    128   In this case `5` is the 0 based index in the list of users the field is currently
    129   at.
    130   """
    131   def path(%{path: path}) do
    132     path
    133     |> Enum.reverse()
    134     |> Enum.drop(1)
    135     |> Enum.map(&field_name/1)
    136   end
    137 
    138   defp field_name(%{alias: nil, name: name}), do: name
    139   defp field_name(%{alias: name}), do: name
    140   defp field_name(%{name: name}), do: name
    141   defp field_name(index), do: index
    142 
    143   @doc """
    144   Get the child fields under the current field.
    145 
    146   ## Example
    147 
    148   Given a document like:
    149   ```graphql
    150   { user { id name }}
    151   ```
    152 
    153   ```
    154   field :user, :user do
    155     resolve fn _, info ->
    156       child_fields = Absinthe.Resolution.project(info) |> Enum.map(&(&1.name))
    157       # ...
    158     end
    159   end
    160   ```
    161 
    162   `child_fields` will be `["id", "name"]`.
    163 
    164   It correctly handles fragments, so for example if you had the document:
    165   ```graphql
    166   {
    167     user {
    168       ... on User {
    169         id
    170       }
    171       ... on Named {
    172         name
    173       }
    174     }
    175   }
    176   ```
    177 
    178   you would still get a nice and simple `child_fields` that was `["id", "name"]`.
    179   """
    180   def project(
    181         %{
    182           definition: %{selections: selections},
    183           path: path,
    184           fields_cache: cache
    185         } = info,
    186         type
    187       ) do
    188     type = Absinthe.Schema.lookup_type(info.schema, type)
    189 
    190     {fields, _} = Absinthe.Resolution.Projector.project(selections, type, path, cache, info)
    191 
    192     fields
    193   end
    194 
    195   defp need_concrete_type_error() do
    196     """
    197     You tried to project from a field that is an abstract type without concrete type information!
    198     Use `project/2` instead of `project/1`, and supply the type yourself please!
    199     """
    200   end
    201 
    202   def call(%{state: :unresolved} = res, resolution_function) do
    203     result =
    204       case resolution_function do
    205         fun when is_function(fun, 2) ->
    206           fun.(res.arguments, res)
    207 
    208         fun when is_function(fun, 3) ->
    209           fun.(res.source, res.arguments, res)
    210 
    211         {mod, fun} ->
    212           apply(mod, fun, [res.source, res.arguments, res])
    213 
    214         _ ->
    215           raise Absinthe.ExecutionError, """
    216           Field resolve property must be a 2 arity anonymous function, 3 arity
    217           anonymous function, or a `{Module, :function}` tuple.
    218 
    219           Instead got: #{inspect(resolution_function)}
    220 
    221           Resolving field:
    222 
    223               #{res.definition.name}
    224 
    225           Defined at:
    226 
    227               #{res.definition.schema_node.__reference__.location.file}:#{
    228             res.definition.schema_node.__reference__.location.line
    229           }
    230 
    231           Info: #{inspect(res)}
    232           """
    233       end
    234 
    235     put_result(res, result)
    236   end
    237 
    238   def call(res, _), do: res
    239 
    240   def path_string(%__MODULE__{path: path}) do
    241     Enum.map(path, fn
    242       %{name: name, alias: alias} ->
    243         alias || name
    244 
    245       %{schema_node: schema_node} ->
    246         schema_node.name
    247     end)
    248   end
    249 
    250   @doc """
    251   Handy function for applying user function result tuples to a resolution struct
    252 
    253   User facing functions generally return one of several tuples like `{:ok, val}`
    254   or `{:error, reason}`. This function handles applying those various tuples
    255   to the resolution struct.
    256 
    257   The resolution state is updated depending on the tuple returned. `:ok` and
    258   `:error` tuples set the state to `:resolved`, whereas middleware tuples set it
    259   to `:unresolved`.
    260 
    261   This is useful for middleware that wants to handle user facing functions, but
    262   does not want to duplicate this logic.
    263   """
    264   def put_result(res, {:ok, value}) do
    265     %{res | state: :resolved, value: value}
    266   end
    267 
    268   def put_result(res, {:error, [{_, _} | _] = error_keyword}) do
    269     %{res | state: :resolved, errors: [error_keyword]}
    270   end
    271 
    272   def put_result(res, {:error, errors}) do
    273     %{res | state: :resolved, errors: List.wrap(errors)}
    274   end
    275 
    276   def put_result(res, {:plugin, module, opts}) do
    277     put_result(res, {:middleware, module, opts})
    278   end
    279 
    280   def put_result(res, {:middleware, module, opts}) do
    281     %{res | state: :unresolved, middleware: [{module, opts} | res.middleware]}
    282   end
    283 
    284   def put_result(res, result) do
    285     raise result_error(result, res.definition, res.source)
    286   end
    287 
    288   @doc false
    289   def result_error({:error, _} = value, field, source) do
    290     result_error(
    291       value,
    292       field,
    293       source,
    294       "You're returning an :error tuple, but did you forget to include a `:message`\nkey in every custom error (map or keyword list)?"
    295     )
    296   end
    297 
    298   def result_error(value, field, source) do
    299     result_error(
    300       value,
    301       field,
    302       source,
    303       "Did you forget to return a valid `{:ok, any}` | `{:error, error_value}` tuple?"
    304     )
    305   end
    306 
    307   @doc """
    308   TODO: Deprecate
    309   """
    310   def call(resolution_function, parent, args, field_info) do
    311     case resolution_function do
    312       fun when is_function(fun, 2) ->
    313         fun.(args, field_info)
    314 
    315       fun when is_function(fun, 3) ->
    316         fun.(parent, args, field_info)
    317 
    318       {mod, fun} ->
    319         apply(mod, fun, [parent, args, field_info])
    320 
    321       _ ->
    322         raise Absinthe.ExecutionError, """
    323         Field resolve property must be a 2 arity anonymous function, 3 arity
    324         anonymous function, or a `{Module, :function}` tuple.
    325         Instead got: #{inspect(resolution_function)}
    326         Info: #{inspect(field_info)}
    327         """
    328     end
    329   end
    330 
    331   def call(function, args, info) do
    332     call(function, info.source, args, info)
    333   end
    334 
    335   @error_detail """
    336   ## For a data result
    337 
    338   `{:ok, any}` result will do.
    339 
    340   ### Examples:
    341 
    342   A simple integer result:
    343 
    344       {:ok, 1}
    345 
    346   Something more complex:
    347 
    348       {:ok, %Model.Thing{some: %{complex: :data}}}
    349 
    350   ## For an error result
    351 
    352   One or more errors for a field can be returned in a single `{:error, error_value}` tuple.
    353 
    354   `error_value` can be:
    355   - A simple error message string.
    356   - A map containing `:message` key, plus any additional serializable metadata.
    357   - A keyword list containing a `:message` key, plus any additional serializable metadata.
    358   - A list containing multiple of any/all of these.
    359   - Any other value compatible with `to_string/1`.
    360 
    361   ### Examples
    362 
    363   A simple error message:
    364 
    365       {:error, "Something bad happened"}
    366 
    367   Multiple error messages:
    368 
    369       {:error, ["Something bad", "Even worse"]
    370 
    371   Single custom errors (note the required `:message` keys):
    372 
    373       {:error, message: "Unknown user", code: 21}
    374       {:error, %{message: "A database error occurred", details: format_db_error(some_value)}}
    375 
    376   Three errors of mixed types:
    377 
    378       {:error, ["Simple message", [message: "A keyword list error", code: 1], %{message: "A map error"}]}
    379 
    380   Generic handler for interoperability with errors from other libraries:
    381 
    382       {:error, :foo}
    383       {:error, 1.0}
    384       {:error, 2}
    385 
    386   ## To activate a plugin
    387 
    388   `{:plugin, NameOfPluginModule, term}` to activate a plugin.
    389 
    390   See `Absinthe.Resolution.Plugin` for more information.
    391 
    392   """
    393   def result_error(value, field, source, guess) do
    394     Absinthe.ExecutionError.exception("""
    395     Invalid value returned from resolver.
    396 
    397     Resolving field:
    398 
    399         #{field.name}
    400 
    401     Defined at:
    402 
    403         #{field.schema_node.__reference__.location.file}:#{
    404       field.schema_node.__reference__.location.line
    405     }
    406 
    407     Resolving on:
    408 
    409         #{inspect(source)}
    410 
    411     Got value:
    412 
    413         #{inspect(value)}
    414 
    415     ...
    416 
    417     #{guess}
    418 
    419     ...
    420 
    421     The result must be one of the following...
    422 
    423     #{@error_detail}
    424     """)
    425   end
    426 end
    427 
    428 defimpl Inspect, for: Absinthe.Resolution do
    429   import Inspect.Algebra
    430 
    431   def inspect(res, opts) do
    432     # TODO: better inspect representation
    433     inner =
    434       res
    435       |> Map.from_struct()
    436       |> Map.update!(:fields_cache, fn _ ->
    437         "#fieldscache<...>"
    438       end)
    439       |> Map.to_list()
    440       |> Inspect.List.inspect(opts)
    441 
    442     concat(["#Absinthe.Resolution<", inner, ">"])
    443   end
    444 end