From d4f97febf5367562e0a6853756505ffbc9fe5ee6 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Sun, 29 Aug 2021 15:23:28 -0400 Subject: [PATCH] improvement: support create/update metadata --- lib/graphql/resolver.ex | 21 +++++++- lib/resource/resource.ex | 98 ++++++++++++++++++++++++++-------- mix.exs | 2 +- mix.lock | 10 ++-- test/create_test.exs | 42 +++++++++++++++ test/support/resources/post.ex | 15 ++++++ 6 files changed, 158 insertions(+), 30 deletions(-) diff --git a/lib/graphql/resolver.ex b/lib/graphql/resolver.ex index ae638f9..1d6708f 100644 --- a/lib/graphql/resolver.ex +++ b/lib/graphql/resolver.ex @@ -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 -> diff --git a/lib/resource/resource.ex b/lib/resource/resource.ex index ec59d5b..6d23f78 100644 --- a/lib/resource/resource.ex +++ b/lib/resource/resource.ex @@ -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 diff --git a/mix.exs b/mix.exs index 611a9f4..76b9e60 100644 --- a/mix.exs +++ b/mix.exs @@ -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"}, diff --git a/mix.lock b/mix.lock index 145c197..6d7f3cc 100644 --- a/mix.lock +++ b/mix.lock @@ -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"}, diff --git a/test/create_test.exs b/test/create_test.exs index a586d25..3d25cf8 100644 --- a/test/create_test.exs +++ b/test/create_test.exs @@ -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 = """ diff --git a/test/support/resources/post.ex b/test/support/resources/post.ex index d12ede9..5f88d5f 100644 --- a/test/support/resources/post.ex +++ b/test/support/resources/post.ex @@ -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