2020-05-02 04:32:56 +12:00
|
|
|
defmodule AshGraphql do
|
|
|
|
@moduledoc """
|
2020-08-14 10:55:34 +12:00
|
|
|
AshGraphql is a graphql front extension for the Ash framework.
|
|
|
|
|
2020-09-24 13:32:45 +12:00
|
|
|
See the [getting started guide](/getting_started.md) for information on setting it up, and
|
2020-08-14 10:55:34 +12:00
|
|
|
see the `AshGraphql.Resource` documentation for docs on its DSL
|
2020-05-02 04:32:56 +12:00
|
|
|
"""
|
|
|
|
|
2020-08-14 09:39:59 +12:00
|
|
|
defmacro __using__(opts) do
|
2021-05-19 17:29:57 +12:00
|
|
|
quote bind_quoted: [apis: opts[:apis], api: opts[:api]], generated: true do
|
2020-09-24 12:54:57 +12:00
|
|
|
apis =
|
|
|
|
api
|
|
|
|
|> List.wrap()
|
|
|
|
|> Kernel.++(List.wrap(apis))
|
2020-12-02 18:55:25 +13:00
|
|
|
|
|
|
|
apis =
|
|
|
|
apis
|
2020-12-01 13:16:37 +13:00
|
|
|
|> Enum.map(&{&1, false})
|
|
|
|
|> List.update_at(0, fn {api, _} -> {api, true} end)
|
2020-09-24 12:54:57 +12:00
|
|
|
|
2020-12-01 13:19:11 +13:00
|
|
|
for {api, first?} <- apis do
|
2020-09-24 12:54:57 +12:00
|
|
|
defmodule Module.concat(api, AshTypes) do
|
|
|
|
@moduledoc false
|
|
|
|
alias Absinthe.{Blueprint, Phase, Pipeline}
|
|
|
|
|
2021-05-06 06:50:03 +12:00
|
|
|
# Ensures the api is compiled, and any errors are raised
|
|
|
|
_ = api.ash_dsl_config()
|
2020-12-02 18:55:25 +13:00
|
|
|
|
2020-09-24 12:54:57 +12:00
|
|
|
def pipeline(pipeline) do
|
|
|
|
Pipeline.insert_before(
|
|
|
|
pipeline,
|
2021-03-13 03:19:33 +13:00
|
|
|
Absinthe.Phase.Schema.ApplyDeclaration,
|
2020-09-24 12:54:57 +12:00
|
|
|
__MODULE__
|
|
|
|
)
|
|
|
|
end
|
2020-08-17 19:00:13 +12:00
|
|
|
|
2021-05-19 17:29:57 +12:00
|
|
|
@dialyzer {:nowarn_function, {:run, 2}}
|
2020-09-24 12:54:57 +12:00
|
|
|
def run(blueprint, _opts) do
|
|
|
|
api = unquote(api)
|
|
|
|
|
2022-02-01 08:55:14 +13:00
|
|
|
Code.ensure_compiled!(api)
|
|
|
|
registry = Ash.Api.registry(api)
|
|
|
|
|
|
|
|
if registry do
|
|
|
|
Code.ensure_compiled!(registry)
|
|
|
|
end
|
2020-12-02 18:55:25 +13:00
|
|
|
|
|
|
|
blueprint_with_queries =
|
|
|
|
api
|
|
|
|
|> AshGraphql.Api.queries(__MODULE__)
|
|
|
|
|> Enum.reduce(blueprint, fn query, blueprint ->
|
|
|
|
Absinthe.Blueprint.add_field(blueprint, "RootQueryType", query)
|
|
|
|
end)
|
|
|
|
|
|
|
|
blueprint_with_mutations =
|
|
|
|
api
|
|
|
|
|> AshGraphql.Api.mutations(__MODULE__)
|
|
|
|
|> Enum.reduce(blueprint_with_queries, fn mutation, blueprint ->
|
|
|
|
Absinthe.Blueprint.add_field(blueprint, "RootMutationType", mutation)
|
|
|
|
end)
|
|
|
|
|
|
|
|
type_definitions =
|
|
|
|
if unquote(first?) do
|
2021-04-24 10:44:56 +12:00
|
|
|
apis = unquote(Enum.map(apis, &elem(&1, 0)))
|
|
|
|
embedded_types = AshGraphql.get_embedded_types(apis)
|
|
|
|
|
|
|
|
global_enums = AshGraphql.global_enums(apis, __MODULE__, __ENV__)
|
2021-01-13 09:14:35 +13:00
|
|
|
|
2020-12-02 18:55:25 +13:00
|
|
|
AshGraphql.Api.global_type_definitions(__MODULE__) ++
|
2021-01-13 09:14:35 +13:00
|
|
|
AshGraphql.Api.type_definitions(api, __MODULE__) ++
|
2021-04-24 10:44:56 +12:00
|
|
|
global_enums ++
|
2021-01-13 09:14:35 +13:00
|
|
|
embedded_types
|
2020-12-02 18:55:25 +13:00
|
|
|
else
|
|
|
|
AshGraphql.Api.type_definitions(api, __MODULE__)
|
|
|
|
end
|
|
|
|
|
|
|
|
new_defs =
|
|
|
|
List.update_at(blueprint_with_mutations.schema_definitions, 0, fn schema_def ->
|
|
|
|
%{
|
|
|
|
schema_def
|
2021-01-13 09:14:35 +13:00
|
|
|
| type_definitions: schema_def.type_definitions ++ type_definitions
|
2020-12-02 18:55:25 +13:00
|
|
|
}
|
|
|
|
end)
|
|
|
|
|
|
|
|
{:ok, %{blueprint_with_mutations | schema_definitions: new_defs}}
|
2020-08-17 19:00:13 +12:00
|
|
|
end
|
2020-08-14 09:39:59 +12:00
|
|
|
end
|
2020-05-02 04:32:56 +12:00
|
|
|
|
2021-01-13 09:14:35 +13:00
|
|
|
if first? do
|
|
|
|
import_types(Absinthe.Type.Custom)
|
|
|
|
import_types(AshGraphql.Types.JSON)
|
2021-06-22 09:20:24 +12:00
|
|
|
import_types(AshGraphql.Types.JSONString)
|
2021-01-13 09:14:35 +13:00
|
|
|
end
|
|
|
|
|
2020-09-24 12:54:57 +12:00
|
|
|
@pipeline_modifier Module.concat(api, AshTypes)
|
|
|
|
end
|
2020-08-14 09:39:59 +12:00
|
|
|
end
|
2020-05-02 04:32:56 +12:00
|
|
|
end
|
2020-08-15 02:20:47 +12:00
|
|
|
|
2021-04-24 10:44:56 +12:00
|
|
|
def global_enums(apis, schema, env) do
|
|
|
|
apis
|
|
|
|
|> Enum.flat_map(&Ash.Api.resources/1)
|
2021-05-19 06:07:01 +12:00
|
|
|
|> Enum.flat_map(&all_attributes_and_arguments/1)
|
2021-04-24 10:44:56 +12:00
|
|
|
|> only_enum_types()
|
|
|
|
|> Enum.uniq()
|
|
|
|
|> Enum.map(fn type ->
|
|
|
|
%Absinthe.Blueprint.Schema.EnumTypeDefinition{
|
|
|
|
module: schema,
|
|
|
|
name: type.graphql_type() |> to_string() |> Macro.camelize(),
|
|
|
|
values:
|
|
|
|
Enum.map(type.values(), fn value ->
|
|
|
|
%Absinthe.Blueprint.Schema.EnumValueDefinition{
|
|
|
|
module: schema,
|
|
|
|
identifier: value,
|
|
|
|
name: String.upcase(to_string(value)),
|
|
|
|
value: value
|
|
|
|
}
|
|
|
|
end),
|
|
|
|
identifier: type.graphql_type(),
|
|
|
|
__reference__: AshGraphql.Resource.ref(env)
|
|
|
|
}
|
|
|
|
end)
|
|
|
|
end
|
|
|
|
|
2021-05-19 06:07:01 +12:00
|
|
|
defp all_attributes_and_arguments(resource) do
|
|
|
|
resource
|
|
|
|
|> Ash.Resource.Info.public_attributes()
|
|
|
|
|> Enum.concat(all_arguments(resource))
|
2021-09-08 09:06:20 +12:00
|
|
|
|> Enum.concat(Ash.Resource.Info.calculations(resource))
|
2021-05-19 06:07:01 +12:00
|
|
|
|> Enum.flat_map(fn %{type: type} = attr ->
|
|
|
|
if Ash.Type.embedded_type?(type) do
|
|
|
|
[
|
|
|
|
attr
|
|
|
|
| type
|
|
|
|
|> embedded_resource()
|
|
|
|
|> all_attributes_and_arguments()
|
|
|
|
]
|
|
|
|
else
|
|
|
|
[attr]
|
|
|
|
end
|
|
|
|
end)
|
|
|
|
end
|
|
|
|
|
|
|
|
# defp embedded_enums(%{type: type}) do
|
|
|
|
# if Ash.Type.embedded_type?(type) do
|
|
|
|
# type
|
|
|
|
# end
|
|
|
|
|
|
|
|
# end
|
|
|
|
|
2021-04-24 10:44:56 +12:00
|
|
|
defp only_enum_types(attributes) do
|
|
|
|
Enum.flat_map(attributes, fn attribute ->
|
|
|
|
case enum_type(attribute.type) do
|
|
|
|
nil ->
|
|
|
|
[]
|
|
|
|
|
|
|
|
type ->
|
|
|
|
[type]
|
|
|
|
end
|
|
|
|
end)
|
|
|
|
end
|
|
|
|
|
2021-01-13 09:14:35 +13:00
|
|
|
def get_embedded_types(apis) do
|
|
|
|
apis
|
|
|
|
|> Enum.flat_map(&Ash.Api.resources/1)
|
2021-02-23 17:28:01 +13:00
|
|
|
|> Enum.flat_map(fn resource ->
|
|
|
|
resource
|
|
|
|
|> Ash.Resource.Info.public_attributes()
|
2021-04-14 09:49:10 +12:00
|
|
|
|> Enum.concat(all_arguments(resource))
|
2021-09-08 09:06:20 +12:00
|
|
|
|> Enum.concat(Ash.Resource.Info.calculations(resource))
|
2021-02-23 17:28:01 +13:00
|
|
|
|> Enum.map(&{resource, &1})
|
|
|
|
end)
|
|
|
|
|> Enum.filter(fn {_resource, attribute} ->
|
2021-04-14 09:49:10 +12:00
|
|
|
attribute.type
|
|
|
|
|> embedded_resource()
|
|
|
|
|> Ash.Type.embedded_type?()
|
2021-02-23 17:28:01 +13:00
|
|
|
end)
|
2021-01-13 09:14:35 +13:00
|
|
|
|> Enum.map(fn
|
2021-02-23 17:28:01 +13:00
|
|
|
{source_resource, attribute} ->
|
|
|
|
{source_resource, attribute, embedded_resource(attribute.type)}
|
2021-01-13 09:14:35 +13:00
|
|
|
end)
|
2021-02-23 17:28:01 +13:00
|
|
|
|> Enum.flat_map(fn {source_resource, attribute, embedded} ->
|
|
|
|
[{source_resource, attribute, embedded}] ++ get_nested_embedded_types(embedded)
|
2021-01-13 09:14:35 +13:00
|
|
|
end)
|
2021-02-23 17:28:01 +13:00
|
|
|
|> Enum.flat_map(fn {source_resource, attribute, embedded_type} ->
|
|
|
|
if AshGraphql.Resource.type(embedded_type) do
|
|
|
|
[
|
|
|
|
AshGraphql.Resource.type_definition(
|
|
|
|
embedded_type,
|
|
|
|
Module.concat(embedded_type, ShadowApi),
|
|
|
|
__MODULE__
|
|
|
|
),
|
|
|
|
AshGraphql.Resource.embedded_type_input(
|
|
|
|
source_resource,
|
|
|
|
attribute,
|
|
|
|
embedded_type,
|
|
|
|
__MODULE__
|
|
|
|
)
|
2021-05-19 06:07:01 +12:00
|
|
|
] ++
|
|
|
|
AshGraphql.Resource.enum_definitions(embedded_type, __MODULE__)
|
2021-02-23 17:28:01 +13:00
|
|
|
else
|
|
|
|
[
|
|
|
|
AshGraphql.Resource.embedded_type_input(
|
|
|
|
source_resource,
|
|
|
|
attribute,
|
|
|
|
embedded_type,
|
|
|
|
__MODULE__
|
|
|
|
)
|
2021-05-19 06:07:01 +12:00
|
|
|
] ++ AshGraphql.Resource.enum_definitions(embedded_type, __MODULE__)
|
2021-02-23 17:28:01 +13:00
|
|
|
end
|
2021-01-13 09:14:35 +13:00
|
|
|
end)
|
2021-04-14 09:49:10 +12:00
|
|
|
|> Enum.uniq_by(& &1.identifier)
|
|
|
|
end
|
|
|
|
|
|
|
|
defp all_arguments(resource) do
|
|
|
|
resource
|
|
|
|
|> Ash.Resource.Info.actions()
|
|
|
|
|> Enum.flat_map(& &1.arguments)
|
2021-01-13 09:14:35 +13:00
|
|
|
end
|
|
|
|
|
2021-04-24 10:44:56 +12:00
|
|
|
defp enum_type({:array, type}), do: enum_type(type)
|
|
|
|
|
|
|
|
defp enum_type(type) do
|
|
|
|
if is_atom(type) && :erlang.function_exported(type, :values, 0) &&
|
|
|
|
:erlang.function_exported(type, :graphql_type, 0) do
|
|
|
|
type
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2021-02-23 17:28:01 +13:00
|
|
|
defp embedded_resource({:array, type}), do: embedded_resource(type)
|
|
|
|
defp embedded_resource(type), do: type
|
|
|
|
|
2021-01-13 09:14:35 +13:00
|
|
|
defp get_nested_embedded_types(embedded_type) do
|
|
|
|
embedded_type
|
2021-02-23 17:28:01 +13:00
|
|
|
|> Ash.Resource.Info.public_attributes()
|
|
|
|
|> Enum.filter(&Ash.Type.embedded_type?(&1.type))
|
|
|
|
|> Enum.map(fn attribute ->
|
|
|
|
{attribute, embedded_resource(attribute.type)}
|
2021-01-13 09:14:35 +13:00
|
|
|
end)
|
2021-02-23 17:28:01 +13:00
|
|
|
|> Enum.flat_map(fn {attribute, embedded} ->
|
|
|
|
[{embedded_type, attribute, embedded}] ++ get_nested_embedded_types(embedded)
|
2021-01-13 09:14:35 +13:00
|
|
|
end)
|
|
|
|
end
|
|
|
|
|
2020-09-24 12:54:57 +12:00
|
|
|
def add_context(ctx, apis) do
|
|
|
|
dataloader =
|
|
|
|
apis
|
|
|
|
|> List.wrap()
|
|
|
|
|> Enum.reduce(Dataloader.new(), fn api, dataloader ->
|
|
|
|
Dataloader.add_source(
|
|
|
|
dataloader,
|
|
|
|
api,
|
|
|
|
AshGraphql.Dataloader.new(api)
|
|
|
|
)
|
|
|
|
end)
|
|
|
|
|
2020-10-28 19:16:16 +13:00
|
|
|
Map.put(ctx, :loader, dataloader)
|
2020-08-15 02:20:47 +12:00
|
|
|
end
|
2020-05-02 04:32:56 +12:00
|
|
|
end
|