Packages
ash_graphql
0.20.2
1.10.0
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 extension for the Ash framework.
"""
defmacro __using__(opts) do
quote bind_quoted: [apis: opts[:apis], api: opts[:api]], generated: true do
require Ash.Api.Info
apis =
api
|> List.wrap()
|> Kernel.++(List.wrap(apis))
apis =
apis
|> Enum.map(fn
{api, registry} ->
IO.warn("""
It is no longer required to list the registry along with an API when using `AshGraphql`
use AshGraphql, apis: [{My.App.Api, My.App.Registry}]
Can now be stated simply as
use AshGraphql, apis: [My.App.Api]
""")
api
api ->
api
end)
|> Enum.map(fn api -> {api, Ash.Api.Info.depend_on_resources(api), false} end)
|> List.update_at(0, fn {api, resources, _} -> {api, resources, true} end)
schema = __MODULE__
for {api, resources, first?} <- apis do
defmodule Module.concat(api, AshTypes) do
@moduledoc false
alias Absinthe.{Blueprint, Phase, Pipeline}
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)
blueprint = AshGraphql.add_root_types(blueprint, __ENV__)
blueprint_with_queries =
api
|> AshGraphql.Api.queries(unquote(resources), __MODULE__)
|> Enum.reduce(blueprint, fn query, blueprint ->
Absinthe.Blueprint.add_field(blueprint, "RootQueryType", query)
end)
blueprint_with_mutations =
api
|> AshGraphql.Api.mutations(unquote(resources), __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, unquote(schema))
global_enums = AshGraphql.global_enums(apis, unquote(schema), __ENV__)
AshGraphql.Api.global_type_definitions(unquote(schema), __ENV__) ++
AshGraphql.Api.type_definitions(
api,
unquote(resources),
unquote(schema),
__ENV__,
true
) ++
global_enums ++
embedded_types
else
AshGraphql.Api.type_definitions(
api,
unquote(resources),
unquote(schema),
__ENV__,
false
)
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.Info.resources/1)
|> Enum.flat_map(&all_attributes_and_arguments/1)
|> only_enum_types()
|> Enum.uniq()
|> Enum.map(fn type ->
{name, identifier} =
case type do
Ash.Type.DurationName ->
{"DurationName", :duration_name}
type ->
graphql_type = type.graphql_type()
{graphql_type |> to_string() |> Macro.camelize(), graphql_type}
end
%Absinthe.Blueprint.Schema.EnumTypeDefinition{
module: schema,
name: name,
values:
Enum.map(type.values(), fn value ->
%Absinthe.Blueprint.Schema.EnumValueDefinition{
module: schema,
identifier: value,
__reference__: AshGraphql.Resource.ref(env),
name: String.upcase(to_string(value)),
value: value
}
end),
identifier: identifier,
__reference__: AshGraphql.Resource.ref(env)
}
end)
|> Enum.uniq_by(& &1.identifier)
end
defp all_attributes_and_arguments(resource) do
resource
|> Ash.Resource.Info.public_attributes()
|> Enum.concat(all_arguments(resource))
|> Enum.concat(Ash.Resource.Info.calculations(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, schema) do
apis
|> Enum.flat_map(&Ash.Api.Info.resources/1)
|> Enum.flat_map(fn resource ->
resource
|> Ash.Resource.Info.public_attributes()
|> Enum.concat(all_arguments(resource))
|> Enum.concat(Ash.Resource.Info.calculations(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.Info.type(embedded_type) do
[
AshGraphql.Resource.type_definition(
embedded_type,
Module.concat(embedded_type, ShadowApi),
schema
),
AshGraphql.Resource.embedded_type_input(
source_resource,
attribute,
embedded_type,
schema
)
] ++
AshGraphql.Resource.enum_definitions(embedded_type, schema, __ENV__)
else
[
AshGraphql.Resource.embedded_type_input(
source_resource,
attribute,
embedded_type,
schema
)
] ++ AshGraphql.Resource.enum_definitions(embedded_type, schema, __ENV__)
end
end)
|> Enum.uniq_by(& &1.identifier)
end
def add_root_types(%Absinthe.Blueprint{} = blueprint, env) do
%{
blueprint
| schema_definitions: Enum.map(blueprint.schema_definitions, &do_add_root_types(&1, env))
}
end
defp do_add_root_types(%Absinthe.Blueprint.Schema.SchemaDefinition{} = schema, env) do
schema
|> add_root_query_type(env)
|> add_root_mutation_type(env)
end
defp add_root_query_type(schema, env) do
if Enum.any?(schema.type_definitions, &(&1.name == "RootQueryType")) do
schema
else
root_query = %Absinthe.Blueprint.Schema.ObjectTypeDefinition{
name: "RootQueryType",
identifier: :query,
module: AshHqWeb.Schema,
description: nil,
interfaces: [],
interface_blueprints: [],
fields: [],
directives: [],
is_type_of: {:ref, env.module, {Absinthe.Blueprint.Schema.ObjectTypeDefinition, :query}},
source_location: nil,
flags: %{},
imports: [],
errors: [],
__reference__: AshGraphql.Resource.ref(env),
__private__: []
}
%{schema | type_definitions: [root_query | schema.type_definitions]}
end
end
defp add_root_mutation_type(schema, env) do
if Enum.any?(schema.type_definitions, &(&1.name == "RootMutationType")) do
schema
else
root_mutation = %Absinthe.Blueprint.Schema.ObjectTypeDefinition{
name: "RootMutationType",
identifier: :mutation,
module: AshHqWeb.Schema,
description: nil,
interfaces: [],
interface_blueprints: [],
fields: [],
directives: [],
is_type_of:
{:ref, env.module, {Absinthe.Blueprint.Schema.ObjectTypeDefinition, :mutation}},
source_location: nil,
flags: %{},
imports: [],
errors: [],
__reference__: AshGraphql.Resource.ref(env),
__private__: []
}
%{schema | type_definitions: [root_mutation | schema.type_definitions]}
end
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, options \\ []) do
options = Keyword.put(options, :get_policy, :tuples)
empty_dataloader = Dataloader.new(options)
dataloader =
apis
|> List.wrap()
|> Enum.map(fn
{api, _registry} ->
api
api ->
api
end)
|> Enum.reduce(empty_dataloader, fn api, dataloader ->
Dataloader.add_source(
dataloader,
api,
AshGraphql.Dataloader.new(api)
)
end)
Map.put(ctx, :loader, dataloader)
end
end