mirror of
https://github.com/ash-project/ash.git
synced 2024-09-20 05:23:03 +12:00
445 lines
12 KiB
Elixir
445 lines
12 KiB
Elixir
defmodule Ash.Test.Type.UnionTest do
|
|
@moduledoc false
|
|
use ExUnit.Case, async: true
|
|
|
|
alias Ash.Test.Domain, as: Domain
|
|
|
|
defmodule Foo do
|
|
use Ash.Resource, data_layer: :embedded
|
|
|
|
attributes do
|
|
attribute :foo, :string, constraints: [match: ~r/foo/], public?: true
|
|
|
|
attribute :type, :string do
|
|
public?(true)
|
|
writable? false
|
|
default "foo"
|
|
end
|
|
end
|
|
end
|
|
|
|
defmodule Bar do
|
|
use Ash.Resource, data_layer: :embedded
|
|
|
|
attributes do
|
|
attribute :bar, :string, constraints: [match: ~r/bar/], public?: true
|
|
|
|
attribute :type, :string do
|
|
public?(true)
|
|
writable? false
|
|
default "bar"
|
|
end
|
|
end
|
|
end
|
|
|
|
defmodule FooBarUnion do
|
|
use Ash.Type.NewType,
|
|
subtype_of: :union,
|
|
constraints: [
|
|
types: [
|
|
foo: [
|
|
type: Foo,
|
|
cast_tag?: false,
|
|
tag: :type,
|
|
tag_value: :foo
|
|
],
|
|
bar: [
|
|
type: Bar,
|
|
cast_tag?: false,
|
|
tag: :type,
|
|
tag_value: :bar
|
|
]
|
|
]
|
|
]
|
|
end
|
|
|
|
defmodule Example do
|
|
use Ash.Resource, domain: Domain, data_layer: Ash.DataLayer.Ets
|
|
|
|
ets do
|
|
private? true
|
|
end
|
|
|
|
actions do
|
|
default_accept :*
|
|
defaults [:read, :destroy, create: :*, update: :*]
|
|
|
|
update :add_thing do
|
|
require_atomic? false
|
|
argument :new_thing, FooBarUnion, allow_nil?: false
|
|
|
|
change fn changeset, _ ->
|
|
new_thing =
|
|
Ash.Changeset.get_argument(changeset, :new_thing)
|
|
|
|
things = Ash.Changeset.get_attribute(changeset, :things) || []
|
|
|
|
Ash.Changeset.change_attribute(
|
|
changeset,
|
|
:things,
|
|
things ++ [new_thing]
|
|
)
|
|
end
|
|
end
|
|
|
|
update :add_things do
|
|
require_atomic? false
|
|
accept [:things]
|
|
end
|
|
end
|
|
|
|
attributes do
|
|
uuid_primary_key :id
|
|
|
|
attribute :things, {:array, :union} do
|
|
public?(true)
|
|
|
|
constraints items: [
|
|
types: [
|
|
foo: [
|
|
type: Foo,
|
|
cast_tag?: false,
|
|
tag: :type,
|
|
tag_value: :foo
|
|
],
|
|
bar: [
|
|
type: Bar,
|
|
cast_tag?: false,
|
|
tag: :type,
|
|
tag_value: :bar
|
|
]
|
|
]
|
|
]
|
|
end
|
|
|
|
attribute :thing, :union,
|
|
public?: true,
|
|
constraints: [
|
|
types: [
|
|
foo: [
|
|
type: Foo,
|
|
cast_tag?: false,
|
|
tag: :type,
|
|
tag_value: "foo"
|
|
],
|
|
bar: [
|
|
type: Bar,
|
|
cast_tag?: false,
|
|
tag: :type,
|
|
tag_value: "bar"
|
|
]
|
|
]
|
|
]
|
|
end
|
|
end
|
|
|
|
test "it handles simple types" do
|
|
constraints = [
|
|
types: [
|
|
int: [
|
|
type: :integer,
|
|
constraints: [
|
|
max: 10
|
|
]
|
|
],
|
|
string: [
|
|
type: :string
|
|
]
|
|
]
|
|
]
|
|
|
|
{:ok, %{constraints: constraints}} =
|
|
Ash.Type.set_type_transformation(%{type: Ash.Type.Union, constraints: constraints})
|
|
|
|
assert {:ok, %Ash.Union{value: 1, type: :int}} = Ash.Type.cast_input(:union, 1, constraints)
|
|
|
|
assert {:error, _} = Ash.Type.cast_input(:union, 11, constraints)
|
|
end
|
|
|
|
test "it handles changes between native types" do
|
|
constraints = [
|
|
types: [
|
|
foo: [
|
|
type: :integer
|
|
],
|
|
bar: [
|
|
type: :string
|
|
]
|
|
]
|
|
]
|
|
|
|
assert {:ok, %Ash.Union{type: :bar, value: "bar"}} =
|
|
Ash.Type.Union.prepare_change(
|
|
%Ash.Union{type: :foo, value: 1},
|
|
"bar",
|
|
constraints
|
|
)
|
|
|
|
assert {:ok, %Ash.Union{type: :bar, value: "bar"}} =
|
|
Ash.Type.Union.handle_change(
|
|
%Ash.Union{type: :foo, value: 1},
|
|
%Ash.Union{type: :bar, value: "bar"},
|
|
constraints
|
|
)
|
|
|
|
assert {:ok, %Ash.Union{type: :bar, value: "bar"}} =
|
|
Ash.Type.Union.handle_change(
|
|
nil,
|
|
%Ash.Union{type: :bar, value: "bar"},
|
|
constraints
|
|
)
|
|
|
|
assert {:ok, %Ash.Union{type: :bar, value: "bar2"}} =
|
|
Ash.Type.Union.handle_change(
|
|
%Ash.Union{type: :bar, value: "bar1"},
|
|
%Ash.Union{type: :bar, value: "bar2"},
|
|
constraints
|
|
)
|
|
|
|
assert {:ok, %Ash.Union{value: nil, type: :bar}} =
|
|
Ash.Type.Union.handle_change(
|
|
%Ash.Union{type: :bar, value: "bar1"},
|
|
nil,
|
|
constraints
|
|
)
|
|
end
|
|
|
|
test "it handles changes between native and tagged types" do
|
|
constraints = [
|
|
types: [
|
|
foo: [
|
|
type: Foo,
|
|
cast_tag?: false,
|
|
constraints: [domain: Domain],
|
|
tag: :type,
|
|
tag_value: :foo
|
|
],
|
|
bar: [
|
|
type: :string
|
|
]
|
|
]
|
|
]
|
|
|
|
assert {:ok, %Ash.Union{type: :bar, value: "bar"}} =
|
|
Ash.Type.Union.prepare_change(
|
|
%Ash.Union{type: :foo, value: %Foo{}},
|
|
"bar",
|
|
constraints
|
|
)
|
|
|
|
assert {:ok, %Ash.Union{type: :bar, value: "bar"}} =
|
|
Ash.Type.Union.handle_change(
|
|
%Ash.Union{type: :foo, value: %{}},
|
|
%Ash.Union{type: :bar, value: "bar"},
|
|
constraints
|
|
)
|
|
end
|
|
|
|
test "it handles tagged types" do
|
|
constraints = [
|
|
types: [
|
|
foo: [
|
|
type: :map,
|
|
tag: :type,
|
|
tag_value: :foo
|
|
],
|
|
bar: [
|
|
type: :map,
|
|
tag: :type,
|
|
tag_value: :bar
|
|
]
|
|
]
|
|
]
|
|
|
|
{:ok, %{constraints: constraints}} =
|
|
Ash.Type.set_type_transformation(%{type: Ash.Type.Union, constraints: constraints})
|
|
|
|
assert {:ok, %Ash.Union{value: %{type: :foo, bar: 1}, type: :foo}} =
|
|
Ash.Type.cast_input(:union, %{type: :foo, bar: 1}, constraints)
|
|
|
|
assert {:ok, %Ash.Union{value: %{type: :bar, bar: 1}, type: :bar}} =
|
|
Ash.Type.cast_input(:union, %{type: :bar, bar: 1}, constraints)
|
|
|
|
assert {:error, _} =
|
|
Ash.Type.cast_input(:union, %{type: :baz, bar: 1}, constraints)
|
|
end
|
|
|
|
test "it handles paths" do
|
|
constraints = [
|
|
items: [
|
|
types: [
|
|
foo: [
|
|
type: Foo,
|
|
cast_tag?: false,
|
|
constraints: [domain: Domain],
|
|
tag: :type,
|
|
tag_value: :foo
|
|
],
|
|
bar: [
|
|
type: Bar,
|
|
cast_tag?: false,
|
|
constraints: [domain: Domain],
|
|
tag: :type,
|
|
tag_value: :bar
|
|
]
|
|
]
|
|
]
|
|
]
|
|
|
|
{:ok, %{type: type, constraints: constraints}} =
|
|
Ash.Type.set_type_transformation(%{
|
|
type: {:array, Ash.Type.Union},
|
|
constraints: constraints
|
|
})
|
|
|
|
assert {:ok, [%Ash.Union{value: %{type: "foo", foo: "foo"}, type: :foo}]} =
|
|
Ash.Type.cast_input(type, [%{type: :foo, foo: "foo"}], constraints)
|
|
|
|
assert {:error,
|
|
[
|
|
error
|
|
]} =
|
|
Ash.Type.cast_input(type, [%{type: :foo, foo: "bar"}], constraints)
|
|
|
|
for {key, val} <- [
|
|
message: "must match the pattern %{regex}",
|
|
field: :foo,
|
|
regex: "~r/foo/",
|
|
index: 0,
|
|
path: [0]
|
|
] do
|
|
assert error[key] == val
|
|
end
|
|
end
|
|
|
|
test "it handles changing union attribute on a resource" do
|
|
Example
|
|
|> Ash.Changeset.for_create(:create, %{thing: %Foo{type: "foo", foo: "foo"}})
|
|
|> Ash.create!()
|
|
|> Ash.Changeset.new()
|
|
|> Ash.Changeset.change_attribute(:thing, %Bar{type: "bar", bar: "bar"})
|
|
|> Ash.Changeset.for_update(:update)
|
|
|> Ash.update!()
|
|
end
|
|
|
|
test "it handles paths on a resource" do
|
|
Example
|
|
|> Ash.Changeset.for_create(:create, %{things: [%{type: :foo, foo: "bar"}]})
|
|
|> Ash.create()
|
|
end
|
|
|
|
test "it dumps to native as explicit maps by default" do
|
|
constraints = [
|
|
types: [
|
|
foo: [
|
|
type: :map,
|
|
tag: :type,
|
|
tag_value: :foo
|
|
],
|
|
bar: [
|
|
type: :map,
|
|
tag: :type,
|
|
tag_value: :bar
|
|
]
|
|
]
|
|
]
|
|
|
|
{:ok, %{constraints: constraints}} =
|
|
Ash.Type.set_type_transformation(%{type: Ash.Type.Union, constraints: constraints})
|
|
|
|
assert {:ok, %Ash.Union{value: %{type: :foo, bar: 1}, type: :foo} = union} =
|
|
Ash.Type.cast_input(:union, %{type: :foo, bar: 1}, constraints)
|
|
|
|
assert Ash.Type.dump_to_native(Ash.Type.Union, union, constraints) ==
|
|
{:ok, %{"type" => :foo, "value" => %{type: :foo, bar: 1}}}
|
|
end
|
|
|
|
test "it dumps to native as raw maps when configured" do
|
|
constraints = [
|
|
storage: :map_with_tag,
|
|
types: [
|
|
foo: [
|
|
type: :map,
|
|
tag: :type,
|
|
tag_value: :foo
|
|
],
|
|
bar: [
|
|
type: :map,
|
|
tag: :type,
|
|
tag_value: :bar
|
|
]
|
|
]
|
|
]
|
|
|
|
{:ok, %{constraints: constraints}} =
|
|
Ash.Type.set_type_transformation(%{type: Ash.Type.Union, constraints: constraints})
|
|
|
|
assert {:ok, %Ash.Union{value: %{type: :foo, bar: 1}, type: :foo} = union} =
|
|
Ash.Type.cast_input(:union, %{type: :foo, bar: 1}, constraints)
|
|
|
|
assert Ash.Type.dump_to_native(Ash.Type.Union, union, constraints) ==
|
|
{:ok, %{type: :foo, bar: 1}}
|
|
end
|
|
|
|
test "it should cast union arguments appropriately" do
|
|
assert {:ok, _} =
|
|
Example
|
|
|> Ash.Changeset.for_create(:create, %{things: []})
|
|
|> Ash.create!()
|
|
|> Ash.Changeset.for_update(:add_thing, %{new_thing: %{type: :foo, foo: "foo"}})
|
|
|> Ash.update()
|
|
end
|
|
|
|
test "it should handle union arguments appropriately" do
|
|
assert {:ok, _} =
|
|
Example
|
|
|> Ash.Changeset.for_create(:create, %{things: []})
|
|
|> Ash.create!()
|
|
|> Ash.Changeset.for_update(:add_thing, %{
|
|
new_thing: %Ash.Union{type: :foo, value: %{type: :foo, foo: "foo"}}
|
|
})
|
|
|> Ash.update()
|
|
end
|
|
|
|
test "it should cast union arguments appropriately when the array is nil" do
|
|
assert {:ok, _} =
|
|
Example
|
|
|> Ash.Changeset.for_create(:create, %{})
|
|
|> Ash.create!()
|
|
|> Ash.Changeset.for_update(:add_thing, %{new_thing: %{type: :foo, foo: "foo"}})
|
|
|> Ash.update()
|
|
end
|
|
|
|
test "it should handle union arguments appropriately when the array is nil" do
|
|
assert {:ok, _} =
|
|
Example
|
|
|> Ash.Changeset.for_create(:create, %{})
|
|
|> Ash.create!()
|
|
|> Ash.Changeset.for_update(:add_thing, %{
|
|
new_thing: %Ash.Union{type: :foo, value: %{type: :foo, foo: "foo"}}
|
|
})
|
|
|> Ash.update()
|
|
end
|
|
|
|
test "it should handle union attribute" do
|
|
assert {:ok, _} =
|
|
Example
|
|
|> Ash.Changeset.for_create(:create, %{})
|
|
|> Ash.create!()
|
|
|> Ash.Changeset.for_update(:add_things, %{
|
|
things: [%Ash.Union{type: :foo, value: %{type: :foo, foo: "foo"}}]
|
|
})
|
|
|> Ash.update()
|
|
end
|
|
|
|
test "it should handle union attribute appropriately when the array is nil" do
|
|
assert {:ok, _} =
|
|
Example
|
|
|> Ash.Changeset.for_create(:create, %{})
|
|
|> Ash.create!()
|
|
|> Ash.Changeset.for_update(:add_things, %{
|
|
things: [%Ash.Union{type: :foo, value: %{type: :foo, foo: "foo"}}]
|
|
})
|
|
|> Ash.update()
|
|
end
|
|
end
|