improvement: support create/update metadata

This commit is contained in:
Zach Daniel 2021-08-29 15:23:28 -04:00
parent e6b13ead7f
commit d4f97febf5
6 changed files with 158 additions and 30 deletions

View file

@ -228,7 +228,8 @@ defmodule AshGraphql.Graphql.Resolver do
{:ok, value} ->
case load_fields(value, resource, api, resolution, "result") do
{:ok, result} ->
{{:ok, %{result: result, errors: []}}, [changeset, {:ok, result}]}
{{:ok, add_metadata(%{result: result, errors: []}, value, changeset.action)},
[changeset, {:ok, result}]}
{:error, error} ->
{{:ok, %{result: nil, errors: to_errors(List.wrap(error))}},
@ -637,7 +638,8 @@ defmodule AshGraphql.Graphql.Resolver do
{:ok, value} ->
case load_fields(value, resource, api, resolution, "result") do
{:ok, result} ->
{{:ok, %{result: result, errors: []}}, [changeset, {:ok, result}]}
{{:ok, add_metadata(%{result: result, errors: []}, result, changeset.action)},
[changeset, {:ok, result}]}
{:error, error} ->
# Even though the loading of fields failed, the mutation was successful
@ -649,6 +651,21 @@ defmodule AshGraphql.Graphql.Resolver do
end
end
defp add_metadata(result, action_result, action) do
metadata = Map.get(action, :metadata, [])
if Enum.empty?(metadata) do
result
else
metadata =
Map.new(action.metadata, fn metadata ->
{metadata.name, Map.get(action_result.__metadata__ || %{}, metadata.name)}
end)
Map.put(result, :metadata, metadata)
end
end
defp destroy_result(result, initial, resource, changeset, resolution) do
case result do
:ok ->

View file

@ -527,28 +527,49 @@ defmodule AshGraphql.Resource do
"The successful result of the mutation"
end
fields = [
%Absinthe.Blueprint.Schema.FieldDefinition{
description: description,
identifier: :result,
module: schema,
name: "result",
type: Resource.type(resource),
__reference__: ref(__ENV__)
},
%Absinthe.Blueprint.Schema.FieldDefinition{
description: "Any errors generated, if the mutation failed",
identifier: :errors,
module: schema,
name: "errors",
type: %Absinthe.Blueprint.TypeReference.List{
of_type: :mutation_error
},
__reference__: ref(__ENV__)
}
]
metadata_object_type = metadata_field(resource, mutation, schema)
fields =
if metadata_object_type do
fields ++
[
%Absinthe.Blueprint.Schema.FieldDefinition{
description: "Metadata produced by the mutation",
identifier: :metadata,
module: schema,
name: "metadata",
type: metadata_object_type.identifier,
__reference__: ref(__ENV__)
}
]
else
fields
end
result = %Absinthe.Blueprint.Schema.ObjectTypeDefinition{
description: "The result of the #{inspect(mutation.name)} mutation",
fields: [
%Absinthe.Blueprint.Schema.FieldDefinition{
description: description,
identifier: :result,
module: schema,
name: "result",
type: Resource.type(resource),
__reference__: ref(__ENV__)
},
%Absinthe.Blueprint.Schema.FieldDefinition{
description: "Any errors generated, if the mutation failed",
identifier: :errors,
module: schema,
name: "errors",
type: %Absinthe.Blueprint.TypeReference.List{
of_type: :mutation_error
},
__reference__: ref(__ENV__)
}
],
fields: fields,
identifier: String.to_atom("#{mutation.name}_result"),
module: schema,
name: Macro.camelize("#{mutation.name}_result"),
@ -562,7 +583,7 @@ defmodule AshGraphql.Resource do
mutation.type
) do
[] ->
[result]
[result] ++ List.wrap(metadata_object_type)
fields ->
input = %Absinthe.Blueprint.Schema.InputObjectTypeDefinition{
@ -573,11 +594,44 @@ defmodule AshGraphql.Resource do
__reference__: ref(__ENV__)
}
[input, result]
[input, result] ++ List.wrap(metadata_object_type)
end
end)
end
# sobelow_skip ["DOS.StringToAtom"]
defp metadata_field(resource, mutation, schema) do
metadata_fields =
Map.get(mutation.action, :metadata, [])
|> Enum.map(fn metadata ->
field_type =
metadata.type
|> field_type(metadata, resource)
|> maybe_wrap_non_null(not metadata.allow_nil?)
%Absinthe.Blueprint.Schema.FieldDefinition{
description: metadata.description,
identifier: metadata.name,
module: schema,
name: to_string(metadata.name),
type: field_type,
__reference__: ref(__ENV__)
}
end)
if !Enum.empty?(metadata_fields) do
name = "#{mutation.name}_metadata"
%Absinthe.Blueprint.Schema.ObjectTypeDefinition{
fields: metadata_fields,
identifier: String.to_atom(name),
module: schema,
name: Macro.camelize(name),
__reference__: ref(__ENV__)
}
end
end
@doc false
# sobelow_skip ["DOS.StringToAtom"]
def embedded_type_input(source_resource, attribute, resource, schema) do

View file

@ -81,7 +81,7 @@ defmodule AshGraphql.MixProject do
# Run "mix help deps" to learn about dependencies.
defp deps do
[
{:ash, ash_version("~> 1.46 and >= 1.46.2")},
{:ash, ash_version("~> 1.47")},
{:absinthe_plug, "~> 1.4"},
{:absinthe, "~> 1.5.3"},
{:dataloader, "~> 1.0"},

View file

@ -1,7 +1,7 @@
%{
"absinthe": {:hex, :absinthe, "1.5.5", "22b26228f56dc6a1074c52cea9c64e869a0cb2427403bcf9056c422d36c66292", [:mix], [{:dataloader, "~> 1.0.0", [hex: :dataloader, repo: "hexpm", optional: true]}, {:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}, {:nimble_parsec, "~> 0.5 or ~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "41e79ed4bffbab4986493ff4120c948d59871fd08ad5e31195129ce3c01aad58"},
"absinthe_plug": {:hex, :absinthe_plug, "1.5.5", "be913e77df1947ffb654a1cf1a90e28d84dc23241f6404053750bae513ccd52b", [:mix], [{:absinthe, "~> 1.5", [hex: :absinthe, repo: "hexpm", optional: false]}, {:plug, "~> 1.4", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "6c366615d9422444774206aff3448bb9cfb4e849e0c9a94a275085097bc67509"},
"ash": {:hex, :ash, "1.46.2", "264f77a87cecd90580c96ab4b17e8b098a4dedd3323a8d49dfd0548289e8eb40", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.4", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8.0", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.3.5", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.1.5", [hex: :picosat_elixir, repo: "hexpm", optional: false]}, {:timex, ">= 3.0.0", [hex: :timex, repo: "hexpm", optional: false]}], "hexpm", "e9283ea88092b1485403e0503366526d7a047147692460c03174c0818d987ded"},
"ash": {:hex, :ash, "1.47.11", "1ce1f77cb0f687a01880b9573102f516be80c90880dcbe4cb53746e5ed765df9", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.4", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8.0", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.3.5", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: false]}, {:timex, ">= 3.0.0", [hex: :timex, repo: "hexpm", optional: false]}], "hexpm", "02b73a3fb80fd90c54301c69c5713e4422a538da0b2f76a1d0ab3cdadc6d94a2"},
"ashton": {:hex, :ashton, "0.4.1", "d0f7782ac44fa22da7ce544028ee3d2078592a834d8adf3e5b4b6aeb94413a55", [:mix], [], "hexpm", "24db667932517fdbc3f2dae777f28b8d87629271387d4490bc4ae8d9c46ff3d3"},
"bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm", "7af5c7e09fe1d40f76c8e4f9dd2be7cebd83909f31fee7cd0e9eadc567da8353"},
"certifi": {:hex, :certifi, "2.6.1", "dbab8e5e155a0763eea978c913ca280a6b544bfa115633fa20249c3d396d9493", [:rebar3], [], "hexpm", "524c97b4991b3849dd5c17a631223896272c6b0af446778ba4675a1dff53bb7e"},
@ -12,7 +12,7 @@
"decimal": {:hex, :decimal, "2.0.0", "a78296e617b0f5dd4c6caf57c714431347912ffb1d0842e998e9792b5642d697", [:mix], [], "hexpm", "34666e9c55dea81013e77d9d87370fe6cb6291d1ef32f46a1600230b1d44f577"},
"dialyxir": {:hex, :dialyxir, "1.0.0", "6a1fa629f7881a9f5aaf3a78f094b2a51a0357c843871b8bc98824e7342d00a5", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "aeb06588145fac14ca08d8061a142d52753dbc2cf7f0d00fc1013f53f8654654"},
"earmark_parser": {:hex, :earmark_parser, "1.4.12", "b245e875ec0a311a342320da0551da407d9d2b65d98f7a9597ae078615af3449", [:mix], [], "hexpm", "711e2cc4d64abb7d566d43f54b78f7dc129308a63bc103fbd88550d2174b3160"},
"ecto": {:hex, :ecto, "3.6.2", "efdf52acfc4ce29249bab5417415bd50abd62db7b0603b8bab0d7b996548c2bc", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "efad6dfb04e6f986b8a3047822b0f826d9affe8e4ebdd2aeedbfcb14fd48884e"},
"ecto": {:hex, :ecto, "3.7.1", "a20598862351b29f80f285b21ec5297da1181c0442687f9b8329f0445d228892", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "d36e5b39fc479e654cffd4dbe1865d9716e4a9b6311faff799b6f90ab81b8638"},
"elixir_make": {:hex, :elixir_make, "0.6.2", "7dffacd77dec4c37b39af867cedaabb0b59f6a871f89722c25b28fcd4bd70530", [:mix], [], "hexpm", "03e49eadda22526a7e5279d53321d1cced6552f344ba4e03e619063de75348d9"},
"erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"},
"ets": {:hex, :ets, "0.8.1", "8ff9bcda5682b98493f8878fc9dbd990e48d566cba8cce59f7c2a78130da29ea", [:mix], [], "hexpm", "6be41b50adb5bc5c43626f25ea2d0af1f4a242fb3fad8d53f0c67c20b78915cc"},
@ -32,16 +32,16 @@
"metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"},
"mime": {:hex, :mime, "1.5.0", "203ef35ef3389aae6d361918bf3f952fa17a09e8e43b5aa592b93eba05d0fb8d", [:mix], [], "hexpm", "55a94c0f552249fc1a3dd9cd2d3ab9de9d3c89b559c2bd01121f824834f24746"},
"mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm", "f278585650aa581986264638ebf698f8bb19df297f66ad91b18910dfc6e19323"},
"nimble_options": {:hex, :nimble_options, "0.3.5", "a4f6820cdcb4ee444afd78635f323e58e8a5ddf2fbbe9b9d283a99f972034bae", [:mix], [], "hexpm", "f5507cc90033a8d12769522009c80aa9164af6bab245dbd4ad421d008455f1e1"},
"nimble_options": {:hex, :nimble_options, "0.3.6", "365d03c05d43483d3eacf820671dafce5b49d692667b3bb8cae28447fd2414ef", [:mix], [], "hexpm", "1c1d3536c4aee1be2c8f3c691bf27c62dbd88d9bb3a0b1a011913453932e8c15"},
"nimble_parsec": {:hex, :nimble_parsec, "1.1.0", "3a6fca1550363552e54c216debb6a9e95bd8d32348938e13de5eda962c0d7f89", [:mix], [], "hexpm", "08eb32d66b706e913ff748f11694b17981c0b04a33ef470e33e11b3d3ac8f54b"},
"parse_trans": {:hex, :parse_trans, "3.3.1", "16328ab840cc09919bd10dab29e431da3af9e9e7e7e6f0089dd5a2d2820011d8", [:rebar3], [], "hexpm", "07cd9577885f56362d414e8c4c4e6bdf10d43a8767abb92d24cbe8b24c54888b"},
"picosat_elixir": {:hex, :picosat_elixir, "0.1.5", "23673bd3080a4489401e25b4896aff1f1138d47b2f650eab724aad1506188ebb", [:make, :mix], [{:elixir_make, "~> 0.4", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "b30b3c3abd1f4281902d3b5bc9b67e716509092d6243b010c29d8be4a526e8c8"},
"picosat_elixir": {:hex, :picosat_elixir, "0.2.1", "407dcb90755167fd9e3311b60565ff32ed0d234010363406c07cdb4175b95bc5", [:make, :mix], [{:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "68f4bdb2ac3b594209e54625d3d58c9e2e98b90f2ec8e03235f66e88c9eda5fe"},
"plug": {:hex, :plug, "1.11.0", "f17217525597628298998bc3baed9f8ea1fa3f1160aa9871aee6df47a6e4d38e", [:mix], [{:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "2d9c633f0499f9dc5c2fd069161af4e2e7756890b81adcbb2ceaa074e8308876"},
"plug_crypto": {:hex, :plug_crypto, "1.2.1", "5c854427528bf61d159855cedddffc0625e2228b5f30eff76d5a4de42d896ef4", [:mix], [], "hexpm", "6961c0e17febd9d0bfa89632d391d2545d2e0eb73768f5f50305a23961d8782c"},
"sobelow": {:hex, :sobelow, "0.11.0", "cdc17e3a9f1ea78dc55dbe0a03121cb6767fef737c6d9f1e62ee7e78730abccc", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "c57807bfe6f231338b657781f89ef0320b66a0dbe779aa911d6ed27cfa14ae6e"},
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.6", "cf344f5692c82d2cd7554f5ec8fd961548d4fd09e7d22f5b62482e5aeaebd4b0", [:make, :mix, :rebar3], [], "hexpm", "bdb0d2471f453c88ff3908e7686f86f9be327d065cc1ec16fa4540197ea04680"},
"telemetry": {:hex, :telemetry, "0.4.3", "a06428a514bdbc63293cd9a6263aad00ddeb66f608163bdec7c8995784080818", [:rebar3], [], "hexpm", "eb72b8365ffda5bed68a620d1da88525e326cb82a75ee61354fc24b844768041"},
"timex": {:hex, :timex, "3.7.5", "3eca56e23bfa4e0848f0b0a29a92fa20af251a975116c6d504966e8a90516dfd", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.10", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 1.0", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "a15608dca680f2ef663d71c95842c67f0af08a0f3b1d00e17bbd22872e2874e4"},
"timex": {:hex, :timex, "3.7.6", "502d2347ec550e77fdf419bc12d15bdccd31266bb7d925b30bf478268098282f", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.10", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 1.0", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "a296327f79cb1ec795b896698c56e662ed7210cc9eb31f0ab365eb3a62e2c589"},
"typable": {:hex, :typable, "0.3.0", "0431e121d124cd26f312123e313d2689b9a5322b15add65d424c07779eaa3ca1", [:mix], [], "hexpm", "880a0797752da1a4c508ac48f94711e04c86156f498065a83d160eef945858f8"},
"tzdata": {:hex, :tzdata, "1.1.0", "72f5babaa9390d0f131465c8702fa76da0919e37ba32baa90d93c583301a8359", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "18f453739b48d3dc5bcf0e8906d2dc112bb40baafe2c707596d89f3c8dd14034"},
"unicode_util_compat": {:hex, :unicode_util_compat, "0.7.0", "bc84380c9ab48177092f43ac89e4dfa2c6d62b40b8bd132b1059ecc7232f9a78", [:rebar3], [], "hexpm", "25eee6d67df61960cf6a794239566599b09e17e668d3700247bc498638152521"},

View file

@ -13,6 +13,48 @@ defmodule AshGraphql.CreateTest do
end)
end
test "metadata is in the result" do
resp =
"""
mutation SimpleCreatePost($input: SimpleCreatePostInput) {
simpleCreatePost(input: $input) {
result{
text
comments(sort:{field:TEXT}){
text
}
}
metadata{
foo
}
errors{
message
}
}
}
"""
|> Absinthe.run(AshGraphql.Test.Schema,
variables: %{"input" => %{"text" => "foobar"}}
)
assert {:ok, result} = resp
refute Map.has_key?(result, :errors)
assert %{
data: %{
"simpleCreatePost" => %{
"result" => %{
"text" => "foobar"
},
"metadata" => %{
"foo" => "bar"
}
}
}
} = result
end
test "a create with a managed relationship works" do
resp =
"""

View file

@ -1,6 +1,17 @@
defmodule AshGraphql.Test.Post do
@moduledoc false
defmodule SetMetadata do
@moduledoc false
use Ash.Resource.Change
def change(changeset, _, _) do
Ash.Changeset.after_action(changeset, fn _changeset, result ->
{:ok, Ash.Resource.Info.put_metadata(result, :foo, "bar")}
end)
end
end
use Ash.Resource,
data_layer: Ash.DataLayer.Ets,
extensions: [AshGraphql.Resource]
@ -27,6 +38,7 @@ defmodule AshGraphql.Test.Post do
end
mutations do
create :simple_create_post, :create
create :create_post, :create_confirm
create :upsert_post, :upsert, upsert?: true
@ -45,6 +57,9 @@ defmodule AshGraphql.Test.Post do
actions do
create :create do
primary?(true)
metadata(:foo, :string)
change(SetMetadata)
end
create :upsert do