Skip to content

Commit

Permalink
Absinthe Query Args
Browse files Browse the repository at this point in the history
  • Loading branch information
trbngr committed May 7, 2021
1 parent 4eefff1 commit 97d11fb
Show file tree
Hide file tree
Showing 9 changed files with 109 additions and 9 deletions.
4 changes: 3 additions & 1 deletion .formatter.exs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ locals_without_parens = [
derive_event: 1,
derive_event: 2,
import_commands: 1,
import_commands: 2
import_commands: 2,
query_args: 1,
query_args: 2
]

[
Expand Down
2 changes: 1 addition & 1 deletion example/.formatter.exs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Used by "mix format"
[
import_deps: [:commanded, :cqrs_tools, :ecto, :phoenix],
import_deps: [:absinthe, :commanded, :cqrs_tools, :ecto, :phoenix],
inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"]
]
4 changes: 4 additions & 0 deletions example/lib/example_api/resolvers/user_resolver.ex
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ defmodule ExampleApi.Resolvers.UserResolver do

import ExecutionResultHelper

def user(args, _res) do
{:ok, Users.get_user!(args)}
end

def users(args, _res) do
args
|> Users.list_users_query!()
Expand Down
13 changes: 10 additions & 3 deletions example/lib/example_api/types/user_types.ex
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
defmodule ExampleApi.Types.UserTypes do
@moduledoc false
use Cqrs.Absinthe
use Absinthe.Schema.Notation
use Absinthe.Relay.Schema.Notation, :modern

alias Example.Queries.{ListUsers, GetUser}

import ExampleApi.Resolvers.UserResolver

enum :user_status do
Expand All @@ -19,10 +23,13 @@ defmodule ExampleApi.Types.UserTypes do
connection(node_type: :user)

object :user_queries do
field :user, :user do
query_args GetUser, except: [:name]
resolve &user/2
end

connection field :users, node_type: :user do
arg :status, :user_status
arg :email, :string
arg :name, :string
query_args ListUsers, status: :user_status
resolve &users/2
end
end
Expand Down
83 changes: 83 additions & 0 deletions lib/cqrs/absinthe.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
if Code.ensure_loaded?(Absinthe) do
defmodule Cqrs.Absinthe do
defmacro __using__(_) do
quote do
import Cqrs.Absinthe, only: [query_args: 1, query_args: 2]
end
end

@doc """
Creates args for an `Absinthe` query from a [query's](`Cqrs.Query`) filters.
## Options
* `:only` - Restrict importing to only the filters listed
* `:except` - Imports all filters except for those listed
* any filter name to an existing absinthe_type
## Examples
field :user, :user do
query_args GetUser, except: [:name]
resolve &user/2
end
connection field :users, node_type: :user do
query_args ListUsers, status: :user_status
resolve &users/2
end
"""
defmacro query_args(query_module, opts \\ []) do
query_args = __create_query_args__(query_module, opts)
Module.eval_quoted(__CALLER__, query_args)
end

def __create_query_args__(query_module, opts) do
quote do
_ = unquote(query_module).__info__(:functions)

unless function_exported?(unquote(query_module), :__query__, 0) do
raise Cqrs.BoundedContext.InvalidQueryError, query: unquote(query_module)
end

filters = Cqrs.Absinthe.__extract_filters__(unquote(query_module), unquote(opts))
|> Enum.map(fn {name, absinthe_type, required} ->
case required do
true -> quote do: arg(unquote(name), non_null(unquote(absinthe_type)))
false -> quote do: arg(unquote(name), unquote(absinthe_type))
end
end)
end
end

def __extract_filters__(query_module, opts) do
filters = query_module.__filters__()

only = Keyword.get(opts, :only, [])
except = Keyword.get(opts, :except, [])

filters =
case {only, except} do
{[], []} -> filters
{[], except} -> Enum.reject(filters, &Enum.member?(except, elem(&1, 0)))
{only, []} -> Enum.filter(filters, &Enum.member?(only, elem(&1, 0)))
_ -> raise "You can only specify :only or :except"
end

Enum.map(filters, fn filter ->
{name, _type, filter_opts} = filter
absinthe_type = Cqrs.Absinthe.__absinthe_type__(filter, opts)
required = Keyword.get(filter_opts, :required, false)
{name, absinthe_type, required}
end)
end

def __absinthe_type__({name, Ecto.Enum, _}, opts) do
enum_type = Keyword.get(opts, name) || raise "Must supply absinthe enum type for #{name}"
quote do: unquote(enum_type)
end

def __absinthe_type__({_name, :binary_id, _}, _opts), do: quote(do: :id)
def __absinthe_type__({_name, type, _}, _opts), do: quote(do: unquote(type))
end
end
5 changes: 3 additions & 2 deletions lib/cqrs/bounded_context.ex
Original file line number Diff line number Diff line change
Expand Up @@ -337,8 +337,9 @@ defmodule Cqrs.BoundedContext do
commands =
case {only, except} do
{[], []} -> commands
{_only, except} -> Enum.reject(commands, &Enum.member?(except, &1))
{only, _except} -> Enum.filter(commands, &Enum.member?(only, &1))
{[], except} -> Enum.reject(commands, &Enum.member?(except, &1))
{only, []} -> Enum.filter(commands, &Enum.member?(only, &1))
_ -> raise "You can only specify :only or :except"
end

Enum.map(commands, fn module -> BoundedContext.command(module, unquote(opts)) end)
Expand Down
3 changes: 2 additions & 1 deletion lib/cqrs/query.ex
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ defmodule Cqrs.Query do

@filter_docs Documentation.field_docs("Filters", @filters, @required_filters)

def __filters__, do: @filters
def __filter_docs__, do: @filter_docs
def __module_docs__, do: @moduledoc
def __query__, do: String.trim_leading(to_string(__MODULE__), "Elixir.")
Expand Down Expand Up @@ -196,7 +197,7 @@ defmodule Cqrs.Query do
required = Keyword.get(unquote(opts), :required, @require_all_filters)
if required, do: @required_filters(unquote(name))

@filters {unquote(name), unquote(type), unquote(opts)}
@filters {unquote(name), unquote(type), Keyword.put(unquote(opts), :required, required)}
end
end

Expand Down
3 changes: 2 additions & 1 deletion mix.exs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
defmodule CqrsTools.MixProject do
use Mix.Project

@version "0.1.6"
@version "0.1.7"

def project do
[
Expand Down Expand Up @@ -39,6 +39,7 @@ defmodule CqrsTools.MixProject do
[
{:ecto, "~> 3.2"},
{:jason, "~> 1.2", optional: true},
{:absinthe, "~> 1.6", optional: true},
{:ex_doc, "~> 0.24", only: :dev, runtime: false},
{:commanded, "~> 1.2", only: [:dev, :test], runtime: false},
{:elixir_uuid, "~> 1.6", override: true, hex: :uuid_utils, only: :test}
Expand Down
1 change: 1 addition & 0 deletions mix.lock
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
%{
"absinthe": {:hex, :absinthe, "1.6.4", "d2958908b72ce146698de8ccbc03622630471eb0e354e06823aaef183e5067bd", [:mix], [{:dataloader, "~> 1.0.0", [hex: :dataloader, repo: "hexpm", optional: true]}, {:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}, {:nimble_parsec, "~> 0.5 or ~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "6e9c1cf36d86c704cb9a9c78db62d1c2676b03e0f61a28a23fc42749e8cd41ae"},
"backoff": {:hex, :backoff, "1.1.6", "83b72ed2108ba1ee8f7d1c22e0b4a00cfe3593a67dbc792799e8cce9f42f796b", [:rebar3], [], "hexpm", "cf0cfff8995fb20562f822e5cc47d8ccf664c5ecdc26a684cbe85c225f9d7c39"},
"commanded": {:hex, :commanded, "1.2.0", "d0c604e885132cbca875c238b741e0e2059c54395b4087d3d91763ebf06254d2", [:mix], [{:backoff, "~> 1.1", [hex: :backoff, repo: "hexpm", optional: false]}, {:elixir_uuid, "~> 1.2", [hex: :elixir_uuid, repo: "hexpm", optional: false]}, {:jason, "~> 1.2", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.0", [hex: :phoenix_pubsub, repo: "hexpm", optional: true]}], "hexpm", "64e51d04773d0b74568ea1d0886c57e350139438096992ad3456d9d80363d0b5"},
"decimal": {:hex, :decimal, "2.0.0", "a78296e617b0f5dd4c6caf57c714431347912ffb1d0842e998e9792b5642d697", [:mix], [], "hexpm", "34666e9c55dea81013e77d9d87370fe6cb6291d1ef32f46a1600230b1d44f577"},
Expand Down

0 comments on commit 97d11fb

Please sign in to comment.