zf

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

request.ex (5284B)


      1 defmodule Absinthe.Plug.Request do
      2   @moduledoc false
      3 
      4   # This struct is the default return type of Request.parse.
      5   # It contains parsed Request structs -- typically just one,
      6   # but when `batched` is set to true, it can be multiple.
      7   #
      8   # extra_keys: e.g. %{"id": ...} sent by react-relay-network-layer,
      9   #             which need to be merged back into the list of final results
     10   #             before sending it to the client
     11 
     12   import Plug.Conn
     13   alias Absinthe.Plug.Request.Query
     14 
     15   defstruct queries: [],
     16             batch: false,
     17             extra_keys: []
     18 
     19   @type t :: %__MODULE__{
     20           queries: list(Absinthe.Plug.Request.Query.t()),
     21           batch: boolean(),
     22           extra_keys: list(map())
     23         }
     24 
     25   @spec parse(Plug.Conn.t(), map) :: {:ok, Plug.Conn.t(), t} | {:input_error, String.t()}
     26   def parse(conn, config) do
     27     root_value =
     28       config
     29       |> Map.get(:root_value, %{})
     30       |> Map.merge(extract_root_value(conn))
     31 
     32     context =
     33       config
     34       |> Map.get(:context, %{})
     35       |> Map.merge(extract_context(conn, config))
     36 
     37     config = Map.merge(config, %{root_value: root_value, context: context})
     38 
     39     config =
     40       (conn.private[:absinthe] || %{})
     41       |> Enum.reduce(config, fn
     42         # keys we already handled
     43         {k, _}, config when k in [:context, :root_value] ->
     44           config
     45 
     46         {k, v}, config ->
     47           Map.put(config, k, v)
     48       end)
     49 
     50     with {:ok, conn, body, params} <- extract_body_and_params(conn, config),
     51          true <- valid_request?(params) do
     52       {:ok, conn, build_request(body, params, config, batch?: is_batch?(params))}
     53     end
     54   end
     55 
     56   # Plug puts parsed params under the "_json" key when the
     57   # structure is not a map; otherwise it's just the keys themselves,
     58   # and they may sit in the body or in the params
     59 
     60   defp is_batch?(params) do
     61     Map.has_key?(params, "_json") && is_list(params["_json"])
     62   end
     63 
     64   defp valid_request?(%{"_json" => json}) when is_list(json) do
     65     Enum.all?(json, &is_map(&1)) ||
     66       {:input_error, "Invalid request structure. Expecting a list of objects."}
     67   end
     68 
     69   defp valid_request?(%{"_json" => json}) when is_binary(json) do
     70     {:input_error, "Invalid request structure. Expecting an object or list of objects."}
     71   end
     72 
     73   defp valid_request?(_params), do: true
     74 
     75   defp build_request(_body, params, config, batch?: true) do
     76     queries =
     77       Enum.map(params["_json"], fn query ->
     78         Query.parse("", query, config)
     79       end)
     80 
     81     extra_keys =
     82       Enum.map(params["_json"], fn query ->
     83         Map.drop(query, ["query", "variables"])
     84       end)
     85 
     86     %__MODULE__{
     87       queries: queries,
     88       batch: true,
     89       extra_keys: extra_keys
     90     }
     91   end
     92 
     93   defp build_request(body, params, config, batch?: false) do
     94     queries =
     95       body
     96       |> Query.parse(params, config)
     97       |> List.wrap()
     98 
     99     %__MODULE__{
    100       queries: queries,
    101       batch: false
    102     }
    103   end
    104 
    105   #
    106   # BODY / PARAMS
    107   #
    108 
    109   @spec extract_body_and_params(Plug.Conn.t(), map()) :: {:ok, Plug.Conn.t(), String.t(), map()}
    110   defp extract_body_and_params(%{body_params: %{"query" => _}} = conn, _config) do
    111     conn = fetch_query_params(conn)
    112     {:ok, conn, "", conn.params}
    113   end
    114 
    115   defp extract_body_and_params(%{body_params: %{"_json" => _}} = conn, config) do
    116     extract_body_and_params_batched(conn, "", config)
    117   end
    118 
    119   defp extract_body_and_params(conn, config) do
    120     with {:ok, body, conn} <- read_body(conn) do
    121       extract_body_and_params_batched(conn, body, config)
    122     end
    123   end
    124 
    125   defp convert_operations_param(conn = %{params: %{"operations" => operations}})
    126        when is_binary(operations) do
    127     put_in(conn.params["_json"], conn.params["operations"])
    128     |> Map.delete("operations")
    129   end
    130 
    131   defp convert_operations_param(conn), do: conn
    132 
    133   defp extract_body_and_params_batched(conn, body, config) do
    134     conn =
    135       conn
    136       |> fetch_query_params()
    137       |> convert_operations_param()
    138 
    139     with %{"_json" => string} = params when is_binary(string) <- conn.params,
    140          {:ok, decoded} <- config.json_codec.module.decode(string) do
    141       {:ok, conn, body, %{params | "_json" => decoded}}
    142     else
    143       {:error, {:invalid, token, pos}} ->
    144         {:input_error, "Could not parse JSON. Invalid token `#{token}` at position #{pos}"}
    145 
    146       {:error, %{__exception__: true} = exception} ->
    147         {:input_error, "Could not parse JSON. #{Exception.message(exception)}"}
    148 
    149       %{} ->
    150         {:ok, conn, body, conn.params}
    151     end
    152   end
    153 
    154   #
    155   # CONTEXT
    156   #
    157 
    158   @spec extract_context(Plug.Conn.t(), map) :: map
    159   defp extract_context(conn, config) do
    160     config.context
    161     |> Map.merge(conn.private[:absinthe][:context] || %{})
    162     |> Map.merge(uploaded_files(conn))
    163   end
    164 
    165   #
    166   # UPLOADED FILES
    167   #
    168 
    169   @spec uploaded_files(Plug.Conn.t()) :: map
    170   defp uploaded_files(conn) do
    171     files =
    172       conn.params
    173       |> Enum.filter(&match?({_, %Plug.Upload{}}, &1))
    174       |> Map.new()
    175 
    176     %{
    177       __absinthe_plug__: %{
    178         uploads: files
    179       }
    180     }
    181   end
    182 
    183   #
    184   # ROOT VALUE
    185   #
    186 
    187   @spec extract_root_value(Plug.Conn.t()) :: any
    188   defp extract_root_value(conn) do
    189     conn.private[:absinthe][:root_value] || %{}
    190   end
    191 
    192   @spec log(t, atom) :: :ok
    193   def log(request, level) do
    194     Enum.each(request.queries, &Query.log(&1, level))
    195     :ok
    196   end
    197 end