Packages
ash_graphql
0.16.21
1.9.4
1.9.3
1.9.2
1.9.1
1.9.0
1.8.5
1.8.4
1.8.3
1.8.2
1.8.1
1.8.0
1.7.17
1.7.16
1.7.15
1.7.14
1.7.13
1.7.12
1.7.11
1.7.10
1.7.9
1.7.8
1.7.7
1.7.6
1.7.5
1.7.4
1.7.3
1.7.2
1.7.1
1.7.0
1.6.0
1.5.1
1.5.0
1.4.7
1.4.6
1.4.5
1.4.4
1.4.3
1.4.2
1.4.1
1.4.0
1.3.4
1.3.3
1.3.2
1.3.1
1.3.0
1.2.1
1.2.0
1.1.1
1.1.0
1.0.1
1.0.0
1.0.0-rc.5
1.0.0-rc.4
retired
1.0.0-rc.3
1.0.0-rc.2
1.0.0-rc.1
1.0.0-rc.0
0.28.1
0.28.0
0.27.1
0.27.0
0.26.9
0.26.8
0.26.7
0.26.6
0.26.5
0.26.4
0.26.3
0.26.2
0.26.0
0.25.13
0.25.12
0.25.10
0.25.9
0.25.8
0.25.7
0.25.6
0.25.5
0.25.4
0.25.3
0.25.2
0.25.1
0.25.0
0.24.1
0.24.0
0.23.3
0.23.2
0.23.1
0.23.0
0.22.13
0.22.12
0.22.11
0.22.10
0.22.9
0.22.8
0.22.7
0.22.6
0.22.4
0.22.3
0.22.2
0.22.1
0.22.0
0.21.0
retired
0.20.5
0.20.4
0.20.3
0.20.2
0.20.1
0.20.0-rc.3
0.20.0-rc.2
0.20.0-rc.1
0.20.0-rc.0
0.19.0
0.18.0-rc0
0.17.5
0.17.5-rc0
0.17.4
0.17.2
0.17.1
0.17.0
0.16.28
0.16.27
0.16.26
0.16.25
0.16.24
0.16.23
0.16.22
0.16.21
0.16.20
0.16.18-rc5
0.16.18-rc4
0.16.18-rc3
0.16.18-rc2
0.16.18-rc1
0.16.18-rc0
0.16.17
0.16.16
0.16.15
0.16.14
0.16.13
0.16.12
0.16.11
0.16.10
0.16.9
0.16.8
0.16.7
0.16.6
0.16.5
0.16.4
0.16.3
0.16.2
0.16.1
0.16.0
0.15.10
0.15.9
0.15.8
0.15.7
0.15.6
0.15.5
0.15.4
0.15.3
0.15.2
0.15.1
0.15.0
0.14.1
0.14.0
0.13.1
0.13.0
0.12.5
0.12.4
0.12.3
0.12.1
0.12.0
0.10.0
0.9.5
0.9.4
0.9.3
0.9.2
0.9.1
0.9.0
0.8.0
0.7.5
0.7.4
0.7.3
0.7.2
0.7.1
0.7.0
0.6.3
0.6.2
0.6.1
0.6.0
0.5.0
0.4.0
0.3.2
0.3.1
0.3.0
0.2.1
0.2.0
0.1.3
0.1.2
The extension for building GraphQL APIs with Ash
Current section
Files
Jump to
Current section
Files
lib/ash_graphql.ex
defmodule AshGraphql do
@moduledoc """
AshGraphql is a graphql front extension for the Ash framework.
See the [getting started guide](/getting_started.md) for information on setting it up, and
see the `AshGraphql.Resource` documentation for docs on its DSL
"""
defmacro __using__(opts) do
quote bind_quoted: [apis: opts[:apis], api: opts[:api]], generated: true do
apis =
api
|> List.wrap()
|> Kernel.++(List.wrap(apis))
apis =
apis
|> Enum.map(&{&1, false})
|> List.update_at(0, fn {api, _} -> {api, true} end)
for {api, first?} <- apis do
defmodule Module.concat(api, AshTypes) do
@moduledoc false
alias Absinthe.{Blueprint, Phase, Pipeline}
# Ensures the api is compiled, and any errors are raised
_ = api.ash_dsl_config()
def pipeline(pipeline) do
Pipeline.insert_before(
pipeline,
Absinthe.Phase.Schema.ApplyDeclaration,
__MODULE__
)
end
@dialyzer {:nowarn_function, {:run, 2}}
def run(blueprint, _opts) do
api = unquote(api)
Code.ensure_compiled(api)
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
apis = unquote(Enum.map(apis, &elem(&1, 0)))
embedded_types = AshGraphql.get_embedded_types(apis)
global_enums = AshGraphql.global_enums(apis, __MODULE__, __ENV__)
AshGraphql.Api.global_type_definitions(__MODULE__) ++
AshGraphql.Api.type_definitions(api, __MODULE__) ++
global_enums ++
embedded_types
else
AshGraphql.Api.type_definitions(api, __MODULE__)
end
new_defs =
List.update_at(blueprint_with_mutations.schema_definitions, 0, fn schema_def ->
%{
schema_def
| type_definitions: schema_def.type_definitions ++ type_definitions
}
end)
{:ok, %{blueprint_with_mutations | schema_definitions: new_defs}}
end
end
if first? do
import_types(Absinthe.Type.Custom)
import_types(AshGraphql.Types.JSON)
import_types(AshGraphql.Types.JSONString)
end
@pipeline_modifier Module.concat(api, AshTypes)
end
end
end
def global_enums(apis, schema, env) do
apis
|> Enum.flat_map(&Ash.Api.resources/1)
|> Enum.flat_map(&all_attributes_and_arguments/1)
|> 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
defp all_attributes_and_arguments(resource) do
resource
|> Ash.Resource.Info.public_attributes()
|> Enum.concat(all_arguments(resource))
|> 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
defp only_enum_types(attributes) do
Enum.flat_map(attributes, fn attribute ->
case enum_type(attribute.type) do
nil ->
[]
type ->
[type]
end
end)
end
def get_embedded_types(apis) do
apis
|> Enum.flat_map(&Ash.Api.resources/1)
|> Enum.flat_map(fn resource ->
resource
|> Ash.Resource.Info.public_attributes()
|> Enum.concat(all_arguments(resource))
|> Enum.map(&{resource, &1})
end)
|> Enum.filter(fn {_resource, attribute} ->
attribute.type
|> embedded_resource()
|> Ash.Type.embedded_type?()
end)
|> Enum.map(fn
{source_resource, attribute} ->
{source_resource, attribute, embedded_resource(attribute.type)}
end)
|> Enum.flat_map(fn {source_resource, attribute, embedded} ->
[{source_resource, attribute, embedded}] ++ get_nested_embedded_types(embedded)
end)
|> 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__
)
] ++
AshGraphql.Resource.enum_definitions(embedded_type, __MODULE__)
else
[
AshGraphql.Resource.embedded_type_input(
source_resource,
attribute,
embedded_type,
__MODULE__
)
] ++ AshGraphql.Resource.enum_definitions(embedded_type, __MODULE__)
end
end)
|> Enum.uniq_by(& &1.identifier)
end
defp all_arguments(resource) do
resource
|> Ash.Resource.Info.actions()
|> Enum.flat_map(& &1.arguments)
end
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
defp embedded_resource({:array, type}), do: embedded_resource(type)
defp embedded_resource(type), do: type
defp get_nested_embedded_types(embedded_type) do
embedded_type
|> Ash.Resource.Info.public_attributes()
|> Enum.filter(&Ash.Type.embedded_type?(&1.type))
|> Enum.map(fn attribute ->
{attribute, embedded_resource(attribute.type)}
end)
|> Enum.flat_map(fn {attribute, embedded} ->
[{embedded_type, attribute, embedded}] ++ get_nested_embedded_types(embedded)
end)
end
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)
Map.put(ctx, :loader, dataloader)
end
end