query.ex (5397B)
1 defmodule Absinthe.Plug.Request.Query do 2 @moduledoc false 3 4 # A struct containing, among a bunch of config params, 5 # the raw GraphQL document and variables that make up the meat 6 # of a GraphQL request. A GraphQL request can contain multiple Queries. 7 # Queries are fed through a DocumentProvider, and then passed into 8 # the pipeline(s) for processing. 9 10 @enforce_keys [ 11 :document, 12 :operation_name, 13 :root_value, 14 :variables, 15 :raw_options, 16 :params 17 ] 18 19 defstruct [ 20 :document, 21 :operation_name, 22 :root_value, 23 :variables, 24 :raw_options, 25 :params, 26 :adapter, 27 :context, 28 :schema, 29 document_provider_key: nil, 30 pipeline: [], 31 document_provider: nil 32 ] 33 34 @type t :: %__MODULE__{ 35 operation_name: nil | String.t(), 36 root_value: any, 37 variables: map, 38 raw_options: Keyword.t(), 39 document: nil | String.t() | Absinthe.Blueprint.t(), 40 document_provider_key: any, 41 pipeline: Absinthe.Pipeline.t(), 42 document_provider: nil | Absinthe.Plug.DocumentProvider.t(), 43 params: map, 44 adapter: Absinthe.Adapter.t(), 45 context: map, 46 schema: Absinthe.Schema.t() 47 } 48 49 def parse(body, params, config) do 50 # either from 51 with raw_document <- extract_raw_document(body, params), 52 {:ok, variables} <- extract_variables(params, config), 53 operation_name <- extract_operation_name(params) do 54 %__MODULE__{ 55 document: raw_document, 56 operation_name: operation_name, 57 raw_options: config.raw_options, 58 variables: variables, 59 context: config.context, 60 adapter: config.adapter, 61 root_value: config.root_value, 62 schema: config.schema_mod, 63 params: params 64 } 65 |> provide_document(config) 66 end 67 end 68 69 def add_pipeline(query, conn_info, config) do 70 config = Map.merge(config, conn_info) 71 opts = query |> to_pipeline_opts 72 73 {module, fun} = config.pipeline 74 pipeline = apply(module, fun, [config, opts]) 75 76 pipeline = 77 %{query | pipeline: pipeline} 78 |> Absinthe.Plug.DocumentProvider.pipeline() 79 80 %{query | pipeline: pipeline} 81 end 82 83 # 84 # OPERATION NAME 85 # 86 87 @spec extract_operation_name(map) :: nil | String.t() 88 defp extract_operation_name(params) do 89 params["operationName"] 90 |> decode_operation_name 91 end 92 93 # GraphQL.js treats an empty operation name as no operation name. 94 @spec decode_operation_name(nil | String.t()) :: nil | String.t() 95 defp decode_operation_name(""), do: nil 96 defp decode_operation_name(name), do: name 97 98 # 99 # VARIABLES 100 # 101 102 @spec extract_variables(map, map) :: {:ok, map} | {:input_error, String.t()} 103 defp extract_variables(params, %{json_codec: json_codec}) do 104 Map.get(params, "variables", "{}") 105 |> decode_variables(json_codec) 106 end 107 108 defp extract_variables(_, _) do 109 {:error, "No json_codec available"} 110 end 111 112 @spec decode_variables(any, map) :: {:ok, map} | {:input_error, String.t()} 113 defp decode_variables(%{} = variables, _), do: {:ok, variables} 114 defp decode_variables("", _), do: {:ok, %{}} 115 defp decode_variables("null", _), do: {:ok, %{}} 116 defp decode_variables(nil, _), do: {:ok, %{}} 117 118 defp decode_variables(variables, codec) when is_binary(variables) do 119 case codec.module.decode(variables) do 120 {:ok, results} -> 121 {:ok, results} 122 123 _ -> 124 {:input_error, "The variable values could not be decoded"} 125 end 126 end 127 128 defp decode_variables(_variables, _), 129 do: {:input_error, "Variables must be a map."} 130 131 # 132 # DOCUMENT 133 # 134 135 @spec extract_raw_document(nil | String.t(), map) :: nil | String.t() 136 defp extract_raw_document(body, params) do 137 Map.get(params, "query", body) 138 |> normalize_raw_document 139 end 140 141 @spec normalize_raw_document(nil | String.t()) :: nil | String.t() 142 defp normalize_raw_document(""), do: nil 143 defp normalize_raw_document(doc), do: doc 144 145 # 146 # DOCUMENT PROVIDERS 147 # 148 149 @spec calculate_document_providers(map) :: [Absinthe.Plug.DocumentProvider.t(), ...] 150 defp calculate_document_providers(%{document_providers: {module, fun}} = config) 151 when is_atom(fun) do 152 apply(module, fun, [config]) 153 end 154 155 defp calculate_document_providers(%{document_providers: simple_value}) do 156 List.wrap(simple_value) 157 end 158 159 @spec ensure_document_providers!([] | providers) :: providers | no_return 160 when providers: [Absinthe.Plug.DocumentProvider.t(), ...] 161 defp ensure_document_providers!([]) do 162 raise "No document providers found to process request" 163 end 164 165 defp ensure_document_providers!(providers) do 166 providers 167 end 168 169 @spec provide_document(t, map) :: t 170 defp provide_document(query, config) do 171 calculate_document_providers(config) 172 |> ensure_document_providers!() 173 |> Absinthe.Plug.DocumentProvider.process(query) 174 end 175 176 @spec to_pipeline_opts(t) :: Keyword.t() 177 def to_pipeline_opts(query) do 178 {with_raw_options, opts} = 179 query 180 |> Map.from_struct() 181 |> Map.to_list() 182 |> Keyword.split([:raw_options]) 183 184 Keyword.merge(opts, with_raw_options[:raw_options]) 185 end 186 187 # 188 # LOGGING 189 # 190 191 @doc false 192 @spec log(t, Logger.level()) :: :ok 193 def log(query, level) do 194 Absinthe.Logger.log_run(level, { 195 query.document, 196 query.schema, 197 query.pipeline, 198 to_pipeline_opts(query) 199 }) 200 end 201 end