mirror of
https://github.com/ash-project/ash.git
synced 2024-09-20 13:33:20 +12:00
improvement: Forbid reserved field names (#388)
Co-authored-by: Juha Lehtonen <juha.lehtonen@relexsolutions.com>
This commit is contained in:
parent
ace38cd31f
commit
fdebbeb242
5 changed files with 109 additions and 8 deletions
|
@ -275,4 +275,16 @@ defmodule Ash.Resource do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@doc false
|
||||||
|
def reserved_names do
|
||||||
|
[
|
||||||
|
:__struct__,
|
||||||
|
:__meta__,
|
||||||
|
:__metadata__,
|
||||||
|
:__order__,
|
||||||
|
:calculations,
|
||||||
|
:aggregates
|
||||||
|
]
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1210,6 +1210,7 @@ defmodule Ash.Resource.Dsl do
|
||||||
Ash.Resource.Transformers.DefaultAccept,
|
Ash.Resource.Transformers.DefaultAccept,
|
||||||
Ash.Resource.Transformers.SetTypes,
|
Ash.Resource.Transformers.SetTypes,
|
||||||
Ash.Resource.Transformers.RequireUniqueFieldNames,
|
Ash.Resource.Transformers.RequireUniqueFieldNames,
|
||||||
|
Ash.Resource.Transformers.NoReservedFieldNames,
|
||||||
Ash.Resource.Transformers.ValidateRelationshipAttributes,
|
Ash.Resource.Transformers.ValidateRelationshipAttributes,
|
||||||
Ash.Resource.Transformers.ValidateEagerIdentities,
|
Ash.Resource.Transformers.ValidateEagerIdentities,
|
||||||
Ash.Resource.Transformers.ValidateAggregatesSupported,
|
Ash.Resource.Transformers.ValidateAggregatesSupported,
|
||||||
|
|
|
@ -14,7 +14,8 @@ defmodule Ash.Schema do
|
||||||
@primary_key false
|
@primary_key false
|
||||||
|
|
||||||
embedded_schema do
|
embedded_schema do
|
||||||
for attribute <- Ash.Resource.Info.attributes(__MODULE__) do
|
for attribute <- Ash.Resource.Info.attributes(__MODULE__),
|
||||||
|
attribute.name not in Ash.Resource.reserved_names() do
|
||||||
read_after_writes? = attribute.generated? and is_nil(attribute.default)
|
read_after_writes? = attribute.generated? and is_nil(attribute.default)
|
||||||
|
|
||||||
constraint_opts =
|
constraint_opts =
|
||||||
|
@ -57,7 +58,8 @@ defmodule Ash.Schema do
|
||||||
Module.put_attribute(__MODULE__, :ash_struct_fields, field)
|
Module.put_attribute(__MODULE__, :ash_struct_fields, field)
|
||||||
end
|
end
|
||||||
|
|
||||||
for relationship <- Ash.Resource.Info.relationships(__MODULE__) do
|
for relationship <- Ash.Resource.Info.relationships(__MODULE__),
|
||||||
|
relationship.name not in Ash.Resource.reserved_names() do
|
||||||
Module.put_attribute(
|
Module.put_attribute(
|
||||||
__MODULE__,
|
__MODULE__,
|
||||||
:ash_struct_fields,
|
:ash_struct_fields,
|
||||||
|
@ -65,7 +67,8 @@ defmodule Ash.Schema do
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
for aggregate <- Ash.Resource.Info.aggregates(__MODULE__) do
|
for aggregate <- Ash.Resource.Info.aggregates(__MODULE__),
|
||||||
|
aggregate.name not in Ash.Resource.reserved_names() do
|
||||||
{:ok, type} = Aggregate.kind_to_type(aggregate.kind, :string)
|
{:ok, type} = Aggregate.kind_to_type(aggregate.kind, :string)
|
||||||
|
|
||||||
field(aggregate.name, Ash.Type.ecto_type(type), virtual: true)
|
field(aggregate.name, Ash.Type.ecto_type(type), virtual: true)
|
||||||
|
@ -77,7 +80,8 @@ defmodule Ash.Schema do
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
for calculation <- Ash.Resource.Info.calculations(__MODULE__) do
|
for calculation <- Ash.Resource.Info.calculations(__MODULE__),
|
||||||
|
calculation.name not in Ash.Resource.reserved_names() do
|
||||||
{mod, _} = calculation.calculation
|
{mod, _} = calculation.calculation
|
||||||
|
|
||||||
field(calculation.name, Ash.Type.ecto_type(calculation.type), virtual: true)
|
field(calculation.name, Ash.Type.ecto_type(calculation.type), virtual: true)
|
||||||
|
@ -102,7 +106,8 @@ defmodule Ash.Schema do
|
||||||
@primary_key false
|
@primary_key false
|
||||||
|
|
||||||
schema Ash.DataLayer.source(__MODULE__) do
|
schema Ash.DataLayer.source(__MODULE__) do
|
||||||
for attribute <- Ash.Resource.Info.attributes(__MODULE__) do
|
for attribute <- Ash.Resource.Info.attributes(__MODULE__),
|
||||||
|
attribute.name not in Ash.Resource.reserved_names() do
|
||||||
read_after_writes? = attribute.generated? and is_nil(attribute.default)
|
read_after_writes? = attribute.generated? and is_nil(attribute.default)
|
||||||
|
|
||||||
constraint_opts =
|
constraint_opts =
|
||||||
|
@ -145,7 +150,8 @@ defmodule Ash.Schema do
|
||||||
Module.put_attribute(__MODULE__, :ash_struct_fields, field)
|
Module.put_attribute(__MODULE__, :ash_struct_fields, field)
|
||||||
end
|
end
|
||||||
|
|
||||||
for relationship <- Ash.Resource.Info.relationships(__MODULE__) do
|
for relationship <- Ash.Resource.Info.relationships(__MODULE__),
|
||||||
|
relationship.name not in Ash.Resource.reserved_names() do
|
||||||
Module.put_attribute(
|
Module.put_attribute(
|
||||||
__MODULE__,
|
__MODULE__,
|
||||||
:ash_struct_fields,
|
:ash_struct_fields,
|
||||||
|
@ -153,7 +159,8 @@ defmodule Ash.Schema do
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
for aggregate <- Ash.Resource.Info.aggregates(__MODULE__) do
|
for aggregate <- Ash.Resource.Info.aggregates(__MODULE__),
|
||||||
|
aggregate.name not in Ash.Resource.reserved_names() do
|
||||||
{:ok, type} = Aggregate.kind_to_type(aggregate.kind, :string)
|
{:ok, type} = Aggregate.kind_to_type(aggregate.kind, :string)
|
||||||
|
|
||||||
field(aggregate.name, Ash.Type.ecto_type(type), virtual: true)
|
field(aggregate.name, Ash.Type.ecto_type(type), virtual: true)
|
||||||
|
@ -165,7 +172,8 @@ defmodule Ash.Schema do
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
for calculation <- Ash.Resource.Info.calculations(__MODULE__) do
|
for calculation <- Ash.Resource.Info.calculations(__MODULE__),
|
||||||
|
calculation.name not in Ash.Resource.reserved_names() do
|
||||||
{mod, _} = calculation.calculation
|
{mod, _} = calculation.calculation
|
||||||
|
|
||||||
field(calculation.name, Ash.Type.ecto_type(calculation.type), virtual: true)
|
field(calculation.name, Ash.Type.ecto_type(calculation.type), virtual: true)
|
||||||
|
|
60
lib/ash/resource/transformers/no_reserved_field_names.ex
Normal file
60
lib/ash/resource/transformers/no_reserved_field_names.ex
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
defmodule Ash.Resource.Transformers.NoReservedFieldNames do
|
||||||
|
@moduledoc """
|
||||||
|
Confirms that a resource does not use reserved names for field names.
|
||||||
|
|
||||||
|
Reserved field names are: #{inspect(Ash.Resource.reserved_names())}.
|
||||||
|
"""
|
||||||
|
use Spark.Dsl.Transformer
|
||||||
|
|
||||||
|
alias Spark.Dsl.Transformer
|
||||||
|
alias Spark.Error.DslError
|
||||||
|
|
||||||
|
def transform(dsl_state) do
|
||||||
|
resource = Transformer.get_persisted(dsl_state, :module)
|
||||||
|
|
||||||
|
attributes =
|
||||||
|
dsl_state
|
||||||
|
|> Transformer.get_entities([:attributes])
|
||||||
|
|
||||||
|
relationships =
|
||||||
|
dsl_state
|
||||||
|
|> Transformer.get_entities([:relationships])
|
||||||
|
|
||||||
|
calculations =
|
||||||
|
dsl_state
|
||||||
|
|> Transformer.get_entities([:calculations])
|
||||||
|
|
||||||
|
aggregates =
|
||||||
|
dsl_state
|
||||||
|
|> Transformer.get_entities([:aggregates])
|
||||||
|
|
||||||
|
attributes
|
||||||
|
|> Enum.concat(relationships)
|
||||||
|
|> Enum.concat(calculations)
|
||||||
|
|> Enum.concat(aggregates)
|
||||||
|
|> Enum.each(fn %{name: name} = field ->
|
||||||
|
if name in Ash.Resource.reserved_names() do
|
||||||
|
path =
|
||||||
|
case field do
|
||||||
|
%Ash.Resource.Aggregate{kind: kind} -> [:aggregates, kind, name]
|
||||||
|
%Ash.Resource.Calculation{} -> [:calculations, name]
|
||||||
|
%Ash.Resource.Attribute{} -> [:attributes, name]
|
||||||
|
%{type: type} -> [:relationships, type, name]
|
||||||
|
end
|
||||||
|
|
||||||
|
raise DslError,
|
||||||
|
message: """
|
||||||
|
Field #{name} is using a reserved name.
|
||||||
|
|
||||||
|
Reserved field names are: #{inspect(Ash.Resource.reserved_names())}
|
||||||
|
""",
|
||||||
|
path: path,
|
||||||
|
module: resource
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
{:ok, dsl_state}
|
||||||
|
end
|
||||||
|
|
||||||
|
def after_compile?, do: true
|
||||||
|
end
|
|
@ -91,5 +91,25 @@ defmodule Ash.Test.Resource.AttributesTest do
|
||||||
end
|
end
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "raises if you pass a reserved name for `name`" do
|
||||||
|
for name <- Ash.Resource.reserved_names() do
|
||||||
|
assert_raise(
|
||||||
|
Spark.Error.DslError,
|
||||||
|
~r/Field #{name} is using a reserved name/,
|
||||||
|
fn ->
|
||||||
|
defmodule :"Elixir.Resource#{name}" do
|
||||||
|
@moduledoc false
|
||||||
|
use Ash.Resource
|
||||||
|
|
||||||
|
attributes do
|
||||||
|
uuid_primary_key :id
|
||||||
|
attribute name, :string
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in a new issue