2020-05-02 04:32:56 +12:00
|
|
|
defmodule AshGraphql do
|
|
|
|
@moduledoc """
|
2022-10-22 00:38:33 +13:00
|
|
|
AshGraphql is a GraphQL extension for the Ash framework.
|
2023-02-09 16:59:20 +13:00
|
|
|
|
|
|
|
For more information, see the [getting started guide](/documentation/tutorials/getting-started-with-graphql.md)
|
2020-05-02 04:32:56 +12:00
|
|
|
"""
|
|
|
|
|
2022-11-02 14:58:41 +13:00
|
|
|
defmacro mutation(do: block) do
|
|
|
|
empty? = !match?({:__block__, _, []}, block)
|
|
|
|
|
|
|
|
quote bind_quoted: [empty?: empty?, block: Macro.escape(block)] do
|
|
|
|
require Absinthe.Schema
|
|
|
|
|
|
|
|
if empty? ||
|
|
|
|
Enum.any?(
|
|
|
|
@ash_resources,
|
|
|
|
fn resource ->
|
|
|
|
!Enum.empty?(AshGraphql.Resource.Info.mutations(resource))
|
|
|
|
end
|
|
|
|
) do
|
|
|
|
Code.eval_quoted(
|
|
|
|
quote do
|
|
|
|
Absinthe.Schema.mutation do
|
|
|
|
unquote(block)
|
|
|
|
end
|
|
|
|
end,
|
|
|
|
[],
|
|
|
|
__ENV__
|
|
|
|
)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2020-08-14 09:39:59 +12:00
|
|
|
defmacro __using__(opts) do
|
2023-02-23 06:19:50 +13:00
|
|
|
quote bind_quoted: [
|
|
|
|
apis: opts[:apis],
|
|
|
|
api: opts[:api],
|
|
|
|
action_middleware: opts[:action_middleware] || []
|
|
|
|
],
|
|
|
|
generated: true do
|
2022-10-22 01:51:31 +13:00
|
|
|
require Ash.Api.Info
|
|
|
|
|
2022-11-02 14:58:41 +13:00
|
|
|
import Absinthe.Schema,
|
|
|
|
except: [
|
|
|
|
mutation: 1
|
|
|
|
]
|
|
|
|
|
|
|
|
import AshGraphql,
|
|
|
|
only: [
|
|
|
|
mutation: 1
|
|
|
|
]
|
|
|
|
|
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
|
2022-05-11 03:49:00 +12:00
|
|
|
|> Enum.map(fn
|
|
|
|
{api, registry} ->
|
2022-10-22 01:51:31 +13:00
|
|
|
IO.warn("""
|
|
|
|
It is no longer required to list the registry along with an API when using `AshGraphql`
|
2020-09-24 12:54:57 +12:00
|
|
|
|
2022-10-22 01:51:31 +13:00
|
|
|
use AshGraphql, apis: [{My.App.Api, My.App.Registry}]
|
|
|
|
|
|
|
|
Can now be stated simply as
|
|
|
|
|
|
|
|
use AshGraphql, apis: [My.App.Api]
|
|
|
|
""")
|
2022-04-27 04:46:51 +12:00
|
|
|
|
2022-10-22 01:51:31 +13:00
|
|
|
api
|
2022-05-11 03:49:00 +12:00
|
|
|
|
2022-10-22 01:51:31 +13:00
|
|
|
api ->
|
|
|
|
api
|
2022-05-11 03:49:00 +12:00
|
|
|
end)
|
2022-10-22 01:51:31 +13:00
|
|
|
|> 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)
|
2022-04-27 04:46:51 +12:00
|
|
|
|
2022-11-02 14:58:41 +13:00
|
|
|
@ash_resources Enum.flat_map(apis, &elem(&1, 1))
|
2023-02-09 19:00:36 +13:00
|
|
|
ash_resources = @ash_resources
|
2022-11-02 14:58:41 +13:00
|
|
|
|
2022-10-21 08:03:13 +13:00
|
|
|
schema = __MODULE__
|
2022-11-01 06:07:15 +13:00
|
|
|
schema_env = __ENV__
|
2022-10-21 08:03:13 +13:00
|
|
|
|
2022-10-22 01:51:31 +13:00
|
|
|
for {api, resources, first?} <- apis do
|
2020-09-24 12:54:57 +12:00
|
|
|
defmodule Module.concat(api, AshTypes) do
|
|
|
|
@moduledoc false
|
|
|
|
alias Absinthe.{Blueprint, Phase, Pipeline}
|
|
|
|
|
|
|
|
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)
|
2023-02-23 06:19:50 +13:00
|
|
|
action_middleware = unquote(action_middleware)
|
2020-09-24 12:54:57 +12:00
|
|
|
|
2020-12-02 18:55:25 +13:00
|
|
|
blueprint_with_queries =
|
|
|
|
api
|
2023-02-23 06:19:50 +13:00
|
|
|
|> AshGraphql.Api.queries(unquote(resources), action_middleware, __MODULE__)
|
2020-12-02 18:55:25 +13:00
|
|
|
|> Enum.reduce(blueprint, fn query, blueprint ->
|
|
|
|
Absinthe.Blueprint.add_field(blueprint, "RootQueryType", query)
|
|
|
|
end)
|
|
|
|
|
|
|
|
blueprint_with_mutations =
|
|
|
|
api
|
2023-02-23 06:19:50 +13:00
|
|
|
|> AshGraphql.Api.mutations(unquote(resources), action_middleware, __MODULE__)
|
2020-12-02 18:55:25 +13:00
|
|
|
|> 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)))
|
2023-02-16 09:10:22 +13:00
|
|
|
|
|
|
|
embedded_types =
|
|
|
|
AshGraphql.get_embedded_types(unquote(ash_resources), unquote(schema))
|
2021-04-24 10:44:56 +12:00
|
|
|
|
2023-02-09 19:00:36 +13:00
|
|
|
global_enums =
|
|
|
|
AshGraphql.global_enums(unquote(ash_resources), unquote(schema), __ENV__)
|
2021-01-13 09:14:35 +13:00
|
|
|
|
2023-04-21 19:12:20 +12:00
|
|
|
global_unions =
|
|
|
|
AshGraphql.global_unions(unquote(ash_resources), unquote(schema), __ENV__)
|
|
|
|
|
2023-05-16 01:39:32 +12:00
|
|
|
Enum.uniq_by(
|
|
|
|
AshGraphql.Api.global_type_definitions(unquote(schema), __ENV__) ++
|
|
|
|
AshGraphql.Api.type_definitions(
|
|
|
|
api,
|
|
|
|
unquote(resources),
|
|
|
|
unquote(schema),
|
|
|
|
__ENV__,
|
|
|
|
true
|
|
|
|
) ++
|
|
|
|
global_enums ++
|
|
|
|
global_unions ++
|
|
|
|
embedded_types,
|
|
|
|
& &1.identifier
|
|
|
|
)
|
2020-12-02 18:55:25 +13:00
|
|
|
else
|
2022-10-22 01:51:31 +13:00
|
|
|
AshGraphql.Api.type_definitions(
|
|
|
|
api,
|
|
|
|
unquote(resources),
|
|
|
|
unquote(schema),
|
|
|
|
__ENV__,
|
|
|
|
false
|
|
|
|
)
|
2020-12-02 18:55:25 +13:00
|
|
|
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
|
|
|
|
2023-02-09 19:00:36 +13:00
|
|
|
def global_enums(resources, schema, env) do
|
|
|
|
resources
|
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 ->
|
2022-02-01 09:06:06 +13:00
|
|
|
{name, identifier} =
|
|
|
|
case type do
|
2022-05-24 08:33:27 +12:00
|
|
|
Ash.Type.DurationName ->
|
|
|
|
{"DurationName", :duration_name}
|
2022-02-01 09:06:06 +13:00
|
|
|
|
|
|
|
type ->
|
|
|
|
graphql_type = type.graphql_type()
|
|
|
|
{graphql_type |> to_string() |> Macro.camelize(), graphql_type}
|
|
|
|
end
|
|
|
|
|
2021-04-24 10:44:56 +12:00
|
|
|
%Absinthe.Blueprint.Schema.EnumTypeDefinition{
|
|
|
|
module: schema,
|
2022-02-01 09:06:06 +13:00
|
|
|
name: name,
|
2021-04-24 10:44:56 +12:00
|
|
|
values:
|
|
|
|
Enum.map(type.values(), fn value ->
|
2023-05-24 09:11:58 +12:00
|
|
|
name =
|
|
|
|
if function_exported?(type, :graphql_rename_value, 1) do
|
|
|
|
type.graphql_rename_value(value)
|
|
|
|
else
|
|
|
|
value
|
|
|
|
end
|
|
|
|
|
2021-04-24 10:44:56 +12:00
|
|
|
%Absinthe.Blueprint.Schema.EnumValueDefinition{
|
|
|
|
module: schema,
|
|
|
|
identifier: value,
|
2022-03-26 06:17:35 +13:00
|
|
|
__reference__: AshGraphql.Resource.ref(env),
|
2023-05-24 09:11:58 +12:00
|
|
|
name: String.upcase(to_string(name)),
|
2021-04-24 10:44:56 +12:00
|
|
|
value: value
|
|
|
|
}
|
|
|
|
end),
|
2022-02-01 09:06:06 +13:00
|
|
|
identifier: identifier,
|
2021-04-24 10:44:56 +12:00
|
|
|
__reference__: AshGraphql.Resource.ref(env)
|
|
|
|
}
|
|
|
|
end)
|
2022-03-25 10:05:53 +13:00
|
|
|
|> Enum.uniq_by(& &1.identifier)
|
2021-04-24 10:44:56 +12:00
|
|
|
end
|
|
|
|
|
2023-04-21 19:12:20 +12:00
|
|
|
def global_unions(resources, schema, env) do
|
|
|
|
resources
|
|
|
|
|> Enum.flat_map(fn resource ->
|
|
|
|
resource
|
|
|
|
|> AshGraphql.Resource.global_unions()
|
|
|
|
|> Enum.flat_map(fn {type, attribute} ->
|
|
|
|
type_name =
|
|
|
|
if function_exported?(type, :graphql_type, 0) do
|
|
|
|
type.graphql_type()
|
|
|
|
else
|
|
|
|
type.graphql_type(attribute.constraints)
|
|
|
|
end
|
|
|
|
|
|
|
|
input_type_name =
|
|
|
|
cond do
|
|
|
|
function_exported?(type, :graphql_input_type, 0) ->
|
|
|
|
type.graphql_input_type()
|
|
|
|
|
|
|
|
function_exported?(type, :graphql_input_type, 1) ->
|
|
|
|
type.graphql_input_type(attribute.constraints)
|
|
|
|
|
|
|
|
true ->
|
|
|
|
"#{type_name}_input"
|
|
|
|
end
|
|
|
|
|
|
|
|
AshGraphql.Resource.union_type_definitions(
|
|
|
|
resource,
|
|
|
|
attribute,
|
|
|
|
type_name,
|
|
|
|
schema,
|
|
|
|
env,
|
|
|
|
input_type_name
|
|
|
|
)
|
|
|
|
end)
|
|
|
|
end)
|
|
|
|
|> Enum.uniq_by(& &1.identifier)
|
|
|
|
end
|
|
|
|
|
2023-02-16 02:20:35 +13:00
|
|
|
@doc false
|
2023-05-18 14:41:24 +12:00
|
|
|
def all_attributes_and_arguments(
|
|
|
|
resource,
|
|
|
|
already_checked \\ [],
|
|
|
|
nested? \\ true,
|
|
|
|
return_new_checked? \\ false
|
|
|
|
) do
|
2023-02-16 07:37:55 +13:00
|
|
|
if resource in already_checked do
|
2023-05-18 14:41:24 +12:00
|
|
|
if return_new_checked? do
|
|
|
|
{[], already_checked}
|
|
|
|
else
|
|
|
|
[]
|
|
|
|
end
|
2023-02-16 07:37:55 +13:00
|
|
|
else
|
|
|
|
already_checked = [resource | already_checked]
|
|
|
|
|
|
|
|
resource
|
|
|
|
|> Ash.Resource.Info.public_attributes()
|
|
|
|
|> Enum.concat(all_arguments(resource))
|
|
|
|
|> Enum.concat(Ash.Resource.Info.calculations(resource))
|
2023-05-18 14:41:24 +12:00
|
|
|
|> Enum.reduce({[], already_checked}, fn %{type: type} = attr, {acc, already_checked} ->
|
|
|
|
if nested? do
|
|
|
|
constraints = Map.get(attr, :constraints, [])
|
|
|
|
{nested, already_checked} = nested_attrs(type, constraints, already_checked)
|
|
|
|
{[attr | nested] ++ acc, already_checked}
|
|
|
|
else
|
|
|
|
{[attr | acc], already_checked}
|
|
|
|
end
|
|
|
|
end)
|
|
|
|
|> then(fn {attrs, checked} ->
|
|
|
|
attrs = Enum.filter(attrs, &AshGraphql.Resource.Info.show_field?(resource, &1.name))
|
|
|
|
|
|
|
|
if return_new_checked? do
|
|
|
|
{attrs, checked}
|
2023-02-16 07:37:55 +13:00
|
|
|
else
|
2023-05-18 14:41:24 +12:00
|
|
|
attrs
|
2023-02-16 07:37:55 +13:00
|
|
|
end
|
|
|
|
end)
|
|
|
|
end
|
2023-05-18 14:41:24 +12:00
|
|
|
end
|
|
|
|
|
2023-08-01 14:14:11 +12:00
|
|
|
defp nested_attrs({:array, type}, constraints, already_checked) do
|
|
|
|
nested_attrs(type, constraints[:items] || [], already_checked)
|
|
|
|
end
|
|
|
|
|
2023-05-18 14:41:24 +12:00
|
|
|
defp nested_attrs(Ash.Type.Union, constraints, already_checked) do
|
|
|
|
Enum.reduce(
|
|
|
|
constraints[:types] || [],
|
|
|
|
{[], already_checked},
|
|
|
|
fn {_, config}, {attrs, already_checked} ->
|
|
|
|
case config[:type] do
|
|
|
|
{:array, type} ->
|
|
|
|
{new, already_checked} =
|
|
|
|
nested_attrs(type, config[:constraints][:items] || [], already_checked)
|
|
|
|
|
|
|
|
{attrs ++ new, already_checked}
|
|
|
|
|
|
|
|
type ->
|
|
|
|
{new, already_checked} =
|
|
|
|
nested_attrs(type, config[:constraints] || [], already_checked)
|
|
|
|
|
|
|
|
{attrs ++ new, already_checked}
|
|
|
|
end
|
|
|
|
end
|
|
|
|
)
|
|
|
|
end
|
|
|
|
|
|
|
|
defp nested_attrs(type, constraints, already_checked) do
|
|
|
|
cond do
|
|
|
|
Ash.Type.embedded_type?(type) ->
|
|
|
|
type
|
|
|
|
|> unwrap_type()
|
|
|
|
|> all_attributes_and_arguments(already_checked, true, true)
|
|
|
|
|
|
|
|
Ash.Type.NewType.new_type?(type) ->
|
|
|
|
constraints = Ash.Type.NewType.constraints(type, constraints)
|
|
|
|
type = Ash.Type.NewType.subtype_of(type)
|
|
|
|
nested_attrs(type, constraints, already_checked)
|
|
|
|
|
|
|
|
true ->
|
|
|
|
{[], already_checked}
|
|
|
|
end
|
2021-05-19 06:07:01 +12:00
|
|
|
end
|
|
|
|
|
2023-03-14 11:32:36 +13:00
|
|
|
def get_embed(type) do
|
|
|
|
if Ash.Type.NewType.new_type?(type) do
|
|
|
|
Ash.Type.NewType.subtype_of(type)
|
|
|
|
else
|
|
|
|
type
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2023-04-21 19:12:20 +12:00
|
|
|
@doc false
|
|
|
|
def only_union_types(attributes) do
|
|
|
|
Enum.flat_map(attributes, fn attribute ->
|
2023-06-15 02:21:20 +12:00
|
|
|
attribute
|
|
|
|
|> only_union_type()
|
|
|
|
|> List.wrap()
|
|
|
|
end)
|
|
|
|
end
|
2023-04-21 19:12:20 +12:00
|
|
|
|
2023-06-15 02:21:20 +12:00
|
|
|
defp only_union_type(%{type: {:array, type}, constraints: constraints} = attribute) do
|
|
|
|
only_union_type(%{attribute | type: type, constraints: constraints[:items] || []})
|
|
|
|
end
|
2023-04-21 19:12:20 +12:00
|
|
|
|
2023-06-15 02:21:20 +12:00
|
|
|
defp only_union_type(attribute) do
|
|
|
|
this_union_type =
|
|
|
|
case union_type(attribute.type) do
|
|
|
|
nil ->
|
|
|
|
nil
|
|
|
|
|
|
|
|
type ->
|
|
|
|
{type, attribute}
|
|
|
|
end
|
|
|
|
|
|
|
|
attribute = %{
|
|
|
|
attribute
|
|
|
|
| type:
|
2023-04-21 19:12:20 +12:00
|
|
|
attribute.type
|
|
|
|
|> unwrap_type()
|
|
|
|
|> Ash.Type.NewType.subtype_of(),
|
|
|
|
constraints: Ash.Type.NewType.constraints(attribute.type, attribute.constraints)
|
2023-06-15 02:21:20 +12:00
|
|
|
}
|
|
|
|
|
|
|
|
case unwrap_type(attribute.type) do
|
|
|
|
Ash.Type.Union ->
|
|
|
|
attribute.constraints[:types]
|
|
|
|
|> Kernel.||([])
|
|
|
|
|> Enum.flat_map(fn {_name, config} ->
|
|
|
|
case union_type(config[:type]) do
|
2023-04-21 19:12:20 +12:00
|
|
|
nil ->
|
|
|
|
[]
|
|
|
|
|
|
|
|
type ->
|
|
|
|
[{type, attribute}]
|
|
|
|
end
|
2023-06-15 02:21:20 +12:00
|
|
|
end)
|
|
|
|
|
|
|
|
type ->
|
|
|
|
case union_type(type) do
|
|
|
|
nil ->
|
|
|
|
[]
|
|
|
|
|
|
|
|
type ->
|
|
|
|
[{type, attribute}]
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|> Enum.concat(List.wrap(this_union_type))
|
2023-04-21 19:12:20 +12:00
|
|
|
end
|
|
|
|
|
2021-04-24 10:44:56 +12:00
|
|
|
defp only_enum_types(attributes) do
|
|
|
|
Enum.flat_map(attributes, fn attribute ->
|
2023-03-14 11:32:36 +13:00
|
|
|
attribute = %{
|
|
|
|
type:
|
|
|
|
attribute.type
|
|
|
|
|> unwrap_type()
|
|
|
|
|> Ash.Type.NewType.subtype_of(),
|
|
|
|
constraints: Ash.Type.NewType.constraints(attribute.type, attribute.constraints)
|
|
|
|
}
|
|
|
|
|
2023-02-16 09:10:22 +13:00
|
|
|
case unwrap_type(attribute.type) do
|
|
|
|
Ash.Type.Union ->
|
|
|
|
Enum.flat_map(attribute.constraints[:types] || [], fn {_name, config} ->
|
|
|
|
case enum_type(config[:type]) do
|
|
|
|
nil ->
|
|
|
|
[]
|
|
|
|
|
|
|
|
type ->
|
|
|
|
[type]
|
|
|
|
end
|
|
|
|
end)
|
2021-04-24 10:44:56 +12:00
|
|
|
|
|
|
|
type ->
|
2023-02-16 09:10:22 +13:00
|
|
|
case enum_type(type) do
|
|
|
|
nil ->
|
|
|
|
[]
|
|
|
|
|
|
|
|
type ->
|
|
|
|
[type]
|
|
|
|
end
|
2021-04-24 10:44:56 +12:00
|
|
|
end
|
|
|
|
end)
|
|
|
|
end
|
|
|
|
|
2023-04-21 19:12:20 +12:00
|
|
|
defp union_type({:array, type}) do
|
|
|
|
union_type(type)
|
|
|
|
end
|
|
|
|
|
|
|
|
defp union_type(type) do
|
|
|
|
if Ash.Type.NewType.new_type?(type) &&
|
|
|
|
Ash.Type.NewType.subtype_of(type) == Ash.Type.Union &&
|
|
|
|
(function_exported?(type, :graphql_type, 0) ||
|
|
|
|
function_exported?(type, :graphql_type, 1)) do
|
|
|
|
type
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2023-02-16 09:10:22 +13:00
|
|
|
# sobelow_skip ["DOS.BinToAtom"]
|
|
|
|
def get_embedded_types(all_resources, schema) do
|
|
|
|
all_resources
|
2021-02-23 17:28:01 +13:00
|
|
|
|> Enum.flat_map(fn resource ->
|
|
|
|
resource
|
2023-02-16 09:10:22 +13:00
|
|
|
|> all_attributes_and_arguments()
|
2021-02-23 17:28:01 +13:00
|
|
|
|> Enum.map(&{resource, &1})
|
|
|
|
end)
|
2023-02-16 09:10:22 +13:00
|
|
|
|> Enum.flat_map(fn
|
2021-02-23 17:28:01 +13:00
|
|
|
{source_resource, attribute} ->
|
2023-07-04 15:42:40 +12:00
|
|
|
{type, constraints} =
|
|
|
|
case attribute.type do
|
|
|
|
{:array, type} ->
|
|
|
|
{type, attribute.constraints[:items] || []}
|
|
|
|
|
|
|
|
type ->
|
|
|
|
{type, attribute.constraints}
|
|
|
|
end
|
|
|
|
|
2023-03-14 11:32:36 +13:00
|
|
|
attribute = %{
|
|
|
|
attribute
|
|
|
|
| type:
|
2023-07-04 15:42:40 +12:00
|
|
|
type
|
2023-03-14 11:32:36 +13:00
|
|
|
|> Ash.Type.NewType.subtype_of(),
|
2023-07-04 15:42:40 +12:00
|
|
|
constraints: Ash.Type.NewType.constraints(type, constraints)
|
2023-03-14 11:32:36 +13:00
|
|
|
}
|
|
|
|
|
2023-07-04 15:42:40 +12:00
|
|
|
case attribute.type do
|
2023-05-16 01:39:32 +12:00
|
|
|
Ash.Type.Map ->
|
|
|
|
if attribute.constraints[:fields] do
|
|
|
|
{source_resource, attribute}
|
|
|
|
end
|
|
|
|
|
|
|
|
[]
|
|
|
|
|
2023-02-16 09:10:22 +13:00
|
|
|
Ash.Type.Union ->
|
|
|
|
attribute.constraints[:types]
|
|
|
|
|> Kernel.||([])
|
|
|
|
|> Enum.flat_map(fn {name, config} ->
|
|
|
|
if Ash.Type.embedded_type?(config[:type]) do
|
|
|
|
[
|
|
|
|
{source_resource,
|
|
|
|
%{
|
|
|
|
attribute
|
|
|
|
| type: config[:type],
|
|
|
|
constraints: config[:constraints],
|
|
|
|
name: :"#{attribute.name}_#{name}"
|
|
|
|
}}
|
|
|
|
]
|
|
|
|
else
|
|
|
|
[]
|
|
|
|
end
|
|
|
|
end)
|
|
|
|
|
|
|
|
other ->
|
|
|
|
if Ash.Type.embedded_type?(other) do
|
|
|
|
[{source_resource, attribute}]
|
|
|
|
else
|
|
|
|
[]
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end)
|
|
|
|
|> Enum.map(fn {source_resource, attribute} ->
|
|
|
|
type = unwrap_type(attribute.type)
|
|
|
|
Code.ensure_compiled!(type)
|
|
|
|
{source_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} ->
|
2022-08-31 13:08:16 +12:00
|
|
|
if AshGraphql.Resource.Info.type(embedded_type) do
|
2021-02-23 17:28:01 +13:00
|
|
|
[
|
|
|
|
AshGraphql.Resource.type_definition(
|
|
|
|
embedded_type,
|
|
|
|
Module.concat(embedded_type, ShadowApi),
|
2022-10-21 08:03:13 +13:00
|
|
|
schema
|
2021-02-23 17:28:01 +13:00
|
|
|
),
|
|
|
|
AshGraphql.Resource.embedded_type_input(
|
|
|
|
source_resource,
|
|
|
|
attribute,
|
|
|
|
embedded_type,
|
2022-10-21 08:03:13 +13:00
|
|
|
schema
|
2021-02-23 17:28:01 +13:00
|
|
|
)
|
2021-05-19 06:07:01 +12:00
|
|
|
] ++
|
2023-05-16 01:39:32 +12:00
|
|
|
AshGraphql.Resource.enum_definitions(embedded_type, schema, __ENV__) ++
|
|
|
|
AshGraphql.Resource.map_definitions(embedded_type, schema, __ENV__)
|
2021-02-23 17:28:01 +13:00
|
|
|
else
|
2023-02-16 09:10:22 +13:00
|
|
|
[]
|
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
|
2023-02-16 02:20:35 +13:00
|
|
|
action_arguments =
|
|
|
|
resource
|
|
|
|
|> Ash.Resource.Info.actions()
|
2023-06-29 02:20:59 +12:00
|
|
|
|> Enum.filter(&used_in_gql?(resource, &1))
|
2023-02-16 02:20:35 +13:00
|
|
|
|> Enum.flat_map(& &1.arguments)
|
|
|
|
|
|
|
|
calculation_arguments =
|
|
|
|
resource
|
|
|
|
|> Ash.Resource.Info.public_calculations()
|
|
|
|
|> Enum.flat_map(& &1.arguments)
|
|
|
|
|
|
|
|
action_arguments ++ calculation_arguments
|
2021-01-13 09:14:35 +13:00
|
|
|
end
|
2023-06-29 02:20:59 +12:00
|
|
|
|
|
|
|
defp used_in_gql?(resource, %{name: name}) do
|
2023-06-29 02:52:23 +12:00
|
|
|
if Ash.Resource.Info.embedded?(resource) do
|
|
|
|
# We should actually check if any resource refers to this action for this
|
|
|
|
true
|
|
|
|
else
|
|
|
|
mutations = AshGraphql.Resource.Info.mutations(resource)
|
|
|
|
queries = AshGraphql.Resource.Info.queries(resource)
|
2023-06-29 02:20:59 +12:00
|
|
|
|
2023-06-29 02:52:23 +12:00
|
|
|
Enum.any?(mutations, fn mutation ->
|
|
|
|
mutation.action == name || Map.get(mutation, :read_action) == name
|
|
|
|
end) || Enum.any?(queries, &(&1.action == name))
|
|
|
|
end
|
2023-06-29 02:20:59 +12:00
|
|
|
end
|
2021-01-13 09:14:35 +13:00
|
|
|
|
2021-04-24 10:44:56 +12:00
|
|
|
defp enum_type({:array, type}), do: enum_type(type)
|
|
|
|
|
|
|
|
defp enum_type(type) do
|
2023-03-14 11:32:36 +13:00
|
|
|
if is_atom(type) && ensure_compiled?(type) && function_exported?(type, :values, 0) &&
|
|
|
|
(function_exported?(type, :graphql_type, 0) || function_exported?(type, :graphql_type, 1)) do
|
2021-04-24 10:44:56 +12:00
|
|
|
type
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2023-02-09 19:23:41 +13:00
|
|
|
defp ensure_compiled?(type) do
|
|
|
|
Code.ensure_compiled!(type)
|
|
|
|
rescue
|
|
|
|
_ ->
|
|
|
|
false
|
|
|
|
end
|
|
|
|
|
2023-02-16 09:10:22 +13:00
|
|
|
defp unwrap_type({:array, type}), do: unwrap_type(type)
|
|
|
|
defp unwrap_type(type), do: type
|
2021-02-23 17:28:01 +13:00
|
|
|
|
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 ->
|
2023-02-16 09:10:22 +13:00
|
|
|
{attribute, unwrap_type(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
|
|
|
|
|
2023-06-09 16:45:39 +12:00
|
|
|
@deprecated "add_context is no longer necessary"
|
|
|
|
def add_context(ctx, _apis, _options \\ []) do
|
|
|
|
ctx
|
2020-08-15 02:20:47 +12:00
|
|
|
end
|
2020-05-02 04:32:56 +12:00
|
|
|
end
|