Packages

Plug support for AbsinthePlugCache, the GraphQL toolkit for Elixir

Current section

Files

Jump to
absinthe_plug_cache lib absinthe plug document_provider compiled.ex
Raw

lib/absinthe/plug/document_provider/compiled.ex

defmodule AbsinthePlugCache.Plug.DocumentProvider.Compiled do
@moduledoc """
Provide pre-compiled documents for retrieval by request parameter key.
Important: This module shouldn't be used as a document provider itself, but
as a toolkit to build one. See the examples below.
### Examples
Define a new module and `use AbsinthePlugCache.Plug.DocumentProvider.Compiled`:
defmodule MyAppWeb.Schema.Documents do
use AbsinthePlugCache.Plug.DocumentProvider.Compiled
# ... Configure here
end
You can provide documents as literals within the module, by key, using the
`provide/2` macro:
provide "item", "query Item($id: ID!) { item(id: $id) { name } }"
You can also load a map of key value pairs using `provide/1`.
provide %{
"item" => "query Item($id: ID!) { item(id: $id) { name } }",
"time" => "{ currentTime }"
}
This can be used to support loading documents extracted using Apollo's
[persistgraphql](https://github.com/apollographql/persistgraphql) tool by
parsing the file and inverting the key/value pairs.
provide File.read!("/path/to/extracted_queries.json")
|> Jason.decode!
|> Map.new(fn {k, v} -> {v, k} end)
By default, the request parameter that will be used to lookup documents is
`"id"`. You can change this by passing a `:key_param` option to `use`, e.g.:
use AbsinthePlugCache.Plug.DocumentProvider.Compiled, key_param: "lookup_key"
## Configuring
You need to configure `AbsinthePlugCache.Plug` to use any document providers that you create.
(Only `AbsinthePlugCache.Plug.DocumentProviders.Default` is configured by default.)
Make sure that a `Compiled` document provider is placed before the `Default` provider.
See the documentation on `AbsinthePlugCache.Plug.init/1` for more details. Look for the
`:document_providers` option.
"""
defmacro __using__(opts) do
key_param = Keyword.get(opts, :key_param, "id") |> to_string
quote do
@behaviour AbsinthePlugCache.Plug.DocumentProvider
@before_compile {unquote(__MODULE__.Writer), :write}
@absinthe_documents_to_compile %{}
# Can be overridden in the document provider module
@compilation_pipeline Absinthe.Pipeline.for_document(nil, jump_phases: false)
|> Absinthe.Pipeline.before(Absinthe.Phase.Document.Variables)
|> Absinthe.Pipeline.without(Absinthe.Phase.Telemetry)
import unquote(__MODULE__), only: [provide: 2, provide: 1]
def process(request, _) do
do_process(request)
end
defp do_process(%{params: %{unquote(key_param) => document_key}} = request) do
case __absinthe_plug_doc__(:compiled, document_key) do
nil ->
{:cont, request}
document ->
{:halt, %{request | document: document, document_provider_key: document_key}}
end
end
defp do_process(request) do
{:cont, request}
end
@doc """
Determine the remaining pipeline for an request with a pre-compiled
document.
Usually this can be changed simply by setting `@compilation_pipeline` in
your document provider. This may need to be overridden if your compilation
phase is not a subset of the full pipeline.
"""
def pipeline(%{pipeline: as_configured}) do
remaining_pipeline_marker = __absinthe_plug_doc__(:remaining_pipeline)
telemetry_phase = {Absinthe.Phase.Telemetry, event: [:execute, :operation, :start]}
as_configured
|> Absinthe.Pipeline.from(remaining_pipeline_marker)
|> Absinthe.Pipeline.insert_before(remaining_pipeline_marker, telemetry_phase)
end
defoverridable pipeline: 1, process: 2
end
end
@doc ~s"""
Provide a GraphQL document for a given key.
Note that the key will be coerced to a string to ensure compatibility with the expected request parameter.
For more information, see the module-level documentation.
## Examples
provide "foo", \"""
query ShowItem($id: ID!) {
item(id: $id) { name }
}
\"""
"""
@spec provide(any, String.t()) :: Macro.t()
defmacro provide(document_key, document_source) do
quote do
@absinthe_documents_to_compile Map.put(
@absinthe_documents_to_compile,
to_string(unquote(document_key)),
unquote(document_source)
)
end
end
@doc ~s"""
Provide multiple GraphQL documents by key.
Note that keys will be coerced to strings to ensure compatibility with the expected request parameter.
For more information, see the module-level documentation.
## Examples
provide %{
"item" => "query Item($id: ID!) { item(id: $id) { name } }",
"time" => "{ currentTime }"
}
"""
@spec provide(%{any => String.t()}) :: Macro.t()
defmacro provide(documents) do
quote do
@absinthe_documents_to_compile Map.merge(
@absinthe_documents_to_compile,
Map.new(
unquote(documents),
&{to_string(elem(&1, 0)), elem(&1, 1)}
)
)
end
end
@doc """
Lookup a document by id.
## Examples
Get a compiled document:
iex> get(CompiledProvider, "provided")
#Absinthe.Blueprint<>
With an explicit `:compiled` flag:
iex> get(CompiledProvider, "provided", :compiled)
#Absinthe.Blueprint<>
Get the source:
iex> get(CompiledProvider, "provided", :source)
"query Item { item { name } }"
When a value isn't present:
iex> get(CompiledProvider, "not-provided")
nil
"""
@spec get(module, String.t(), :compiled | :source) :: nil | Absinthe.Blueprint.t()
def get(dp, id, format \\ :compiled)
def get(dp, id, :compiled) do
dp.__absinthe_plug_doc__(:compiled, id)
end
def get(dp, id, :source) do
dp.__absinthe_plug_doc__(:source, id)
end
end