defmodule AshGraphql.Resource.Transformers.ValidateCompatibleNames do
@moduledoc "Ensures that all field names are valid or remapped to something valid exist"
use Spark.Dsl.Transformer
alias Spark.Dsl.Transformer
def after_compile?, do: true
def transform(dsl) do
field_names = AshGraphql.Resource.Info.field_names(dsl)
argument_names = AshGraphql.Resource.Info.argument_names(dsl)
resource = Transformer.get_persisted(dsl, :module)
dsl
|> Ash.Resource.Info.public_attributes()
|> Enum.concat(Ash.Resource.Info.public_aggregates(dsl))
|> Enum.concat(Ash.Resource.Info.public_calculations(dsl))
|> Enum.concat(Ash.Resource.Info.public_relationships(dsl))
|> Enum.filter(&AshGraphql.Resource.Info.show_field?(resource, &1.name))
|> Enum.each(fn field ->
name = field_names[field.name] || field.name
if invalid_name?(name) do
raise_invalid_name_error(resource, field, name)
end
end)
dsl
|> Transformer.get_entities([:graphql, :queries])
|> Enum.concat(Transformer.get_entities(dsl, [:graphql, :mutations]))
|> Enum.map(& &1.action)
|> Enum.uniq()
|> Enum.each(fn action ->
action = Ash.Resource.Info.action(dsl, action)
Enum.each(action.arguments, fn argument ->
name = argument_names[action.name][argument.name] || argument.name
if invalid_name?(name) do
raise_invalid_argument_name_error(resource, action, argument.name, name)
end
end)
end)
{:ok, dsl}
end
defp invalid_name?(name) do
Regex.match?(~r/_+\d/, to_string(name))
end
defp raise_invalid_name_error(resource, field, name) do
path =
case field do
%Ash.Resource.Relationships.BelongsTo{} -> [:relationships, :belongs_to, field.name]
%Ash.Resource.Relationships.HasMany{} -> [:relationships, :has_many, field.name]
%Ash.Resource.Relationships.HasOne{} -> [:relationships, :has_one, field.name]
%Ash.Resource.Relationships.ManyToMany{} -> [:relationships, :many_to_many, field.name]
%Ash.Resource.Calculation{} -> [:calculations, field.name]
%Ash.Resource.Aggregate{} -> [:aggregates, field.name]
%Ash.Resource.Attribute{} -> [:attributes, field.name]
end
raise Spark.Error.DslError,
module: resource,
path: path,
message: """
Name #{name} is invalid.
Due to issues in the underlying tooling with camel/snake case conversion of names that
include underscores immediately preceding integers, a different name must be provided to
use in the graphql. To do so, add a mapping in your configured field_names, i.e
graphql do
...
field_names #{name}: :#{make_name_better(name)}
...
end
For more information on the underlying issue, see: https://github.com/absinthe-graphql/absinthe/issues/601
"""
end
defp raise_invalid_argument_name_error(resource, action, argument_name, name) do
path = [:actions, action.type, action.name, :argument, argument_name]
raise Spark.Error.DslError,
module: resource,
path: path,
message: """
Name #{name} is invalid.
Due to issues in the underlying tooling with camel/snake case conversion of names that
include underscores immediately preceding integers, a different name must be provided to
use in the graphql. To do so, add a mapping in your configured argument_names, i.e
graphql do
...
argument_names #{action.name}: [#{argument_name}: :#{make_name_better(name)}]
...
end
For more information on the underlying issue, see: https://github.com/absinthe-graphql/absinthe/issues/601
"""
end
defp make_name_better(name) do
name
|> to_string()
|> String.replace(~r/_+\d/, fn v ->
String.trim_leading(v, "_")
end)
end
end