compiled.ex (6176B)
1 defmodule Absinthe.Plug.DocumentProvider.Compiled do 2 @moduledoc """ 3 4 Provide pre-compiled documents for retrieval by request parameter key. 5 6 Important: This module shouldn't be used as a document provider itself, but 7 as a toolkit to build one. See the examples below. 8 9 ### Examples 10 11 Define a new module and `use Absinthe.Plug.DocumentProvider.Compiled`: 12 13 defmodule MyAppWeb.Schema.Documents do 14 use Absinthe.Plug.DocumentProvider.Compiled 15 16 # ... Configure here 17 18 end 19 20 You can provide documents as literals within the module, by key, using the 21 `provide/2` macro: 22 23 provide "item", "query Item($id: ID!) { item(id: $id) { name } }" 24 25 You can also load a map of key value pairs using `provide/1`. 26 27 provide %{ 28 "item" => "query Item($id: ID!) { item(id: $id) { name } }", 29 "time" => "{ currentTime }" 30 } 31 32 This can be used to support loading documents extracted using Apollo's 33 [persistgraphql](https://github.com/apollographql/persistgraphql) tool by 34 parsing the file and inverting the key/value pairs. 35 36 provide File.read!("/path/to/extracted_queries.json") 37 |> Jason.decode! 38 |> Map.new(fn {k, v} -> {v, k} end) 39 40 By default, the request parameter that will be used to lookup documents is 41 `"id"`. You can change this by passing a `:key_param` option to `use`, e.g.: 42 43 use Absinthe.Plug.DocumentProvider.Compiled, key_param: "lookup_key" 44 45 ## Configuring 46 47 You need to configure `Absinthe.Plug` to use any document providers that you create. 48 (Only `Absinthe.Plug.DocumentProviders.Default` is configured by default.) 49 50 Make sure that a `Compiled` document provider is placed before the `Default` provider. 51 52 See the documentation on `Absinthe.Plug.init/1` for more details. Look for the 53 `:document_providers` option. 54 """ 55 56 defmacro __using__(opts) do 57 key_param = Keyword.get(opts, :key_param, "id") |> to_string 58 59 quote do 60 @behaviour Absinthe.Plug.DocumentProvider 61 62 @before_compile {unquote(__MODULE__.Writer), :write} 63 @absinthe_documents_to_compile %{} 64 65 # Can be overridden in the document provider module 66 @compilation_pipeline Absinthe.Pipeline.for_document(nil, jump_phases: false) 67 |> Absinthe.Pipeline.before(Absinthe.Phase.Document.Variables) 68 |> Absinthe.Pipeline.without(Absinthe.Phase.Telemetry) 69 70 import unquote(__MODULE__), only: [provide: 2, provide: 1] 71 72 def process(request, _) do 73 do_process(request) 74 end 75 76 defp do_process(%{params: %{unquote(key_param) => document_key}} = request) do 77 case __absinthe_plug_doc__(:compiled, document_key) do 78 nil -> 79 {:cont, request} 80 81 document -> 82 {:halt, %{request | document: document, document_provider_key: document_key}} 83 end 84 end 85 86 defp do_process(request) do 87 {:cont, request} 88 end 89 90 @doc """ 91 Determine the remaining pipeline for an request with a pre-compiled 92 document. 93 94 Usually this can be changed simply by setting `@compilation_pipeline` in 95 your document provider. This may need to be overridden if your compilation 96 phase is not a subset of the full pipeline. 97 """ 98 def pipeline(%{pipeline: as_configured}) do 99 remaining_pipeline_marker = __absinthe_plug_doc__(:remaining_pipeline) 100 telemetry_phase = {Absinthe.Phase.Telemetry, event: [:execute, :operation, :start]} 101 102 as_configured 103 |> Absinthe.Pipeline.from(remaining_pipeline_marker) 104 |> Absinthe.Pipeline.insert_before(remaining_pipeline_marker, telemetry_phase) 105 end 106 107 defoverridable pipeline: 1, process: 2 108 end 109 end 110 111 @doc ~s""" 112 Provide a GraphQL document for a given key. 113 114 Note that the key will be coerced to a string to ensure compatibility with the expected request parameter. 115 116 For more information, see the module-level documentation. 117 118 ## Examples 119 120 provide "foo", \""" 121 query ShowItem($id: ID!) { 122 item(id: $id) { name } 123 } 124 \""" 125 126 """ 127 @spec provide(any, String.t()) :: Macro.t() 128 defmacro provide(document_key, document_source) do 129 quote do 130 @absinthe_documents_to_compile Map.put( 131 @absinthe_documents_to_compile, 132 to_string(unquote(document_key)), 133 unquote(document_source) 134 ) 135 end 136 end 137 138 @doc ~s""" 139 Provide multiple GraphQL documents by key. 140 141 Note that keys will be coerced to strings to ensure compatibility with the expected request parameter. 142 143 For more information, see the module-level documentation. 144 145 ## Examples 146 147 provide %{ 148 "item" => "query Item($id: ID!) { item(id: $id) { name } }", 149 "time" => "{ currentTime }" 150 } 151 152 """ 153 @spec provide(%{any => String.t()}) :: Macro.t() 154 defmacro provide(documents) do 155 quote do 156 @absinthe_documents_to_compile Map.merge( 157 @absinthe_documents_to_compile, 158 Map.new( 159 unquote(documents), 160 &{to_string(elem(&1, 0)), elem(&1, 1)} 161 ) 162 ) 163 end 164 end 165 166 @doc """ 167 Lookup a document by id. 168 169 ## Examples 170 171 Get a compiled document: 172 173 iex> get(CompiledProvider, "provided") 174 #Absinthe.Blueprint<> 175 176 With an explicit `:compiled` flag: 177 178 iex> get(CompiledProvider, "provided", :compiled) 179 #Absinthe.Blueprint<> 180 181 Get the source: 182 183 iex> get(CompiledProvider, "provided", :source) 184 "query Item { item { name } }" 185 186 When a value isn't present: 187 188 iex> get(CompiledProvider, "not-provided") 189 nil 190 191 """ 192 @spec get(module, String.t(), :compiled | :source) :: nil | Absinthe.Blueprint.t() 193 def get(dp, id, format \\ :compiled) 194 195 def get(dp, id, :compiled) do 196 dp.__absinthe_plug_doc__(:compiled, id) 197 end 198 199 def get(dp, id, :source) do 200 dp.__absinthe_plug_doc__(:source, id) 201 end 202 end