From c0b8b630a2e9e2ab271febb277d9347267c2d249 Mon Sep 17 00:00:00 2001 From: Matthew Boehlig Date: Tue, 4 Oct 2022 14:21:10 -0500 Subject: [PATCH 01/10] absinthe: handle embedded field validation errors - error uses key.field notation --- .../lib/blunt/absinthe/absinthe_errors.ex | 8 +++- .../test/blunt/absinthe/mutation_test.exs | 37 ++++++++++++++++++- apps/blunt_absinthe/test/support/address.ex | 13 +++++++ .../test/support/create_person.ex | 1 + .../test/support/schema_types.ex | 7 +++- 5 files changed, 61 insertions(+), 5 deletions(-) create mode 100644 apps/blunt_absinthe/test/support/address.ex diff --git a/apps/blunt_absinthe/lib/blunt/absinthe/absinthe_errors.ex b/apps/blunt_absinthe/lib/blunt/absinthe/absinthe_errors.ex index 0578466..3295118 100644 --- a/apps/blunt_absinthe/lib/blunt/absinthe/absinthe_errors.ex +++ b/apps/blunt_absinthe/lib/blunt/absinthe/absinthe_errors.ex @@ -27,7 +27,13 @@ defmodule Blunt.Absinthe.AbsintheErrors do Enum.map(messages, fn message -> [message: message] end) ++ acc {key, messages}, acc when is_list(messages) or is_map(messages) -> - Enum.map(messages, fn message -> [message: "#{key} #{message}"] end) ++ acc + Enum.map(messages, fn + {field, messages} -> + [message: "#{key}.#{field} #{messages |> Enum.join(",")}"] + + message -> + [message: "#{key} #{message}"] + end) ++ acc {key, message}, acc when is_binary(message) -> [[message: "#{key} #{message}"] | acc] diff --git a/apps/blunt_absinthe/test/blunt/absinthe/mutation_test.exs b/apps/blunt_absinthe/test/blunt/absinthe/mutation_test.exs index 87ee3ac..d9680b4 100644 --- a/apps/blunt_absinthe/test/blunt/absinthe/mutation_test.exs +++ b/apps/blunt_absinthe/test/blunt/absinthe/mutation_test.exs @@ -9,8 +9,8 @@ defmodule Blunt.Absinthe.MutationTest do setup do %{ query: """ - mutation create($name: String!, $gender: Gender!){ - createPerson(name: $name, gender: $gender){ + mutation create($name: String!, $gender: Gender!, $address: AddressInput){ + createPerson(name: $name, gender: $gender, address: $address){ id name gender @@ -40,6 +40,39 @@ defmodule Blunt.Absinthe.MutationTest do assert {:ok, _} = UUID.info(id) end + test "returns errors", %{query: query} do + assert {:ok, %{errors: [%{message: message}]}} = + Absinthe.run(query, Schema, variables: %{"name" => "chris", "gender" => ""}) + + assert message =~ "gender" + end + + test "returns errors for absinthe type mismatch", %{query: query} do + assert {:ok, %{errors: [%{message: message}]}} = + Absinthe.run(query, Schema, + variables: %{ + "name" => "chris", + "gender" => "MALE", + "address" => %{} + } + ) + + assert message =~ ~r/address.*\n.*line1.*found null/ + end + + test "returns errors from nested changeset validations", %{query: query} do + assert {:ok, %{errors: [%{message: message}]}} = + Absinthe.run(query, Schema, + variables: %{ + "name" => "chris", + "gender" => "MALE", + "address" => %{"line1" => "--"} + } + ) + + assert message =~ "address.line1 should be at least 3 character(s)" + end + test "user is put in the context from absinthe resolution context", %{query: query} do context = %{user: %{name: "chris"}, reply_to: self()} diff --git a/apps/blunt_absinthe/test/support/address.ex b/apps/blunt_absinthe/test/support/address.ex new file mode 100644 index 0000000..e2901d5 --- /dev/null +++ b/apps/blunt_absinthe/test/support/address.ex @@ -0,0 +1,13 @@ +defmodule Blunt.Absinthe.Test.Address do + use Blunt.ValueObject + import Ecto.Changeset + + field :line1, :string, required: true + field :line2, :string + + @impl true + def handle_validate(changeset, _opts) do + changeset + |> validate_length(:line1, min: 3) + end +end diff --git a/apps/blunt_absinthe/test/support/create_person.ex b/apps/blunt_absinthe/test/support/create_person.ex index 33143a3..c5c82c0 100644 --- a/apps/blunt_absinthe/test/support/create_person.ex +++ b/apps/blunt_absinthe/test/support/create_person.ex @@ -8,6 +8,7 @@ defmodule Blunt.Absinthe.Test.CreatePerson do field :name, :string field :gender, :enum, values: Person.genders(), default: :not_sure + field :address, Blunt.Absinthe.Test.Address, required: false internal_field :id, :binary_id, desc: "Id is set internally. Setting it will have no effect" diff --git a/apps/blunt_absinthe/test/support/schema_types.ex b/apps/blunt_absinthe/test/support/schema_types.ex index d3a3f5f..09bb9f3 100644 --- a/apps/blunt_absinthe/test/support/schema_types.ex +++ b/apps/blunt_absinthe/test/support/schema_types.ex @@ -2,7 +2,7 @@ defmodule Blunt.Absinthe.Test.SchemaTypes do use Blunt.Absinthe use Absinthe.Schema.Notation - alias Blunt.Absinthe.Test.{CreatePerson, GetPerson, UpdatePerson, Dog} + alias Blunt.Absinthe.Test.{CreatePerson, GetPerson, UpdatePerson, Address, Dog} derive_enum :gender, {CreatePerson, :gender} @@ -10,9 +10,12 @@ defmodule Blunt.Absinthe.Test.SchemaTypes do field :id, :id field :name, :string field :gender, :gender + field :address, :address end derive_object(:dog, Dog) + derive_object(:address, Address) + derive_mutation_input Address object :person_queries do derive_query GetPerson, :person, @@ -24,7 +27,7 @@ defmodule Blunt.Absinthe.Test.SchemaTypes do derive_mutation_input(UpdatePerson, arg_types: [gender: :gender]) object :person_mutations do - derive_mutation CreatePerson, :person, arg_types: [gender: :gender] + derive_mutation CreatePerson, :person, arg_types: [gender: :gender, address: :address_input] derive_mutation UpdatePerson, :person, input_object: true end end From 68188cbdcc7896d69f811c721ddaf4398992ac01 Mon Sep 17 00:00:00 2001 From: Matthew Boehlig Date: Wed, 5 Oct 2022 13:10:46 -0500 Subject: [PATCH 02/10] read address after mutation --- .../test/blunt/absinthe/mutation_test.exs | 10 ++++++++-- .../test/support/create_person_handler.ex | 4 ++++ apps/blunt_absinthe/test/support/read_model.ex | 11 +++++++++++ 3 files changed, 23 insertions(+), 2 deletions(-) diff --git a/apps/blunt_absinthe/test/blunt/absinthe/mutation_test.exs b/apps/blunt_absinthe/test/blunt/absinthe/mutation_test.exs index d9680b4..594162d 100644 --- a/apps/blunt_absinthe/test/blunt/absinthe/mutation_test.exs +++ b/apps/blunt_absinthe/test/blunt/absinthe/mutation_test.exs @@ -14,6 +14,10 @@ defmodule Blunt.Absinthe.MutationTest do id name gender + address { + line1 + line2 + } } } """ @@ -34,9 +38,11 @@ defmodule Blunt.Absinthe.MutationTest do test "can create a person", %{query: query} do assert {:ok, %{data: %{"createPerson" => person}}} = - Absinthe.run(query, Schema, variables: %{"name" => "chris", "gender" => "MALE"}) + Absinthe.run(query, Schema, + variables: %{"name" => "chris", "gender" => "MALE", "address" => %{"line1" => "42 Infinity Ave"}} + ) - assert %{"id" => id, "name" => "chris", "gender" => "MALE"} = person + assert %{"id" => id, "name" => "chris", "gender" => "MALE", "address" => %{"line1" => "42 Infinity Ave"}} = person assert {:ok, _} = UUID.info(id) end diff --git a/apps/blunt_absinthe/test/support/create_person_handler.ex b/apps/blunt_absinthe/test/support/create_person_handler.ex index 7568c83..c32f64e 100644 --- a/apps/blunt_absinthe/test/support/create_person_handler.ex +++ b/apps/blunt_absinthe/test/support/create_person_handler.ex @@ -8,7 +8,11 @@ defmodule Blunt.Absinthe.Test.CreatePersonHandler do def handle_dispatch(command, _context) do command |> Map.from_struct() + |> Map.update!(:address, &to_map/1) |> Person.changeset() |> Repo.insert() end + + defp to_map(nil), do: nil + defp to_map(value), do: Map.from_struct(value) end diff --git a/apps/blunt_absinthe/test/support/read_model.ex b/apps/blunt_absinthe/test/support/read_model.ex index 6f6a762..4b6807c 100644 --- a/apps/blunt_absinthe/test/support/read_model.ex +++ b/apps/blunt_absinthe/test/support/read_model.ex @@ -9,12 +9,23 @@ defmodule Blunt.Absinthe.Test.ReadModel do schema "people" do field :name, :string field :gender, Ecto.Enum, values: @genders, default: :not_sure + + embeds_one :address, Address, primary_key: false do + field :line1, :string + field :line2, :string + end end def changeset(person \\ %__MODULE__{}, attrs) do person |> Ecto.Changeset.cast(attrs, [:id, :name, :gender]) |> Ecto.Changeset.validate_required([:id, :gender]) + |> Ecto.Changeset.cast_embed(:address, with: &address_changeset/2) + end + + def address_changeset(address, attrs) do + address + |> Ecto.Changeset.cast(attrs, [:line1, :line2]) end end end From c013c6a02e80fc60d99314739a1b929a03c65c5a Mon Sep 17 00:00:00 2001 From: Matthew Boehlig Date: Wed, 5 Oct 2022 13:14:10 -0500 Subject: [PATCH 03/10] test derive mutation input for embedded field --- apps/blunt_absinthe/test/blunt/absinthe/mutation_test.exs | 3 +++ apps/blunt_absinthe/test/support/schema_types.ex | 2 +- apps/blunt_absinthe/test/support/update_person.ex | 1 + 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/apps/blunt_absinthe/test/blunt/absinthe/mutation_test.exs b/apps/blunt_absinthe/test/blunt/absinthe/mutation_test.exs index 594162d..b58239d 100644 --- a/apps/blunt_absinthe/test/blunt/absinthe/mutation_test.exs +++ b/apps/blunt_absinthe/test/blunt/absinthe/mutation_test.exs @@ -99,6 +99,9 @@ defmodule Blunt.Absinthe.MutationTest do }, gender: %{ type: :gender + }, + address: %{ + type: :address_input } } = fields end diff --git a/apps/blunt_absinthe/test/support/schema_types.ex b/apps/blunt_absinthe/test/support/schema_types.ex index 09bb9f3..6c901ae 100644 --- a/apps/blunt_absinthe/test/support/schema_types.ex +++ b/apps/blunt_absinthe/test/support/schema_types.ex @@ -24,7 +24,7 @@ defmodule Blunt.Absinthe.Test.SchemaTypes do ] end - derive_mutation_input(UpdatePerson, arg_types: [gender: :gender]) + derive_mutation_input(UpdatePerson, arg_types: [gender: :gender, address: :address_input]) object :person_mutations do derive_mutation CreatePerson, :person, arg_types: [gender: :gender, address: :address_input] diff --git a/apps/blunt_absinthe/test/support/update_person.ex b/apps/blunt_absinthe/test/support/update_person.ex index 93c258c..e271f10 100644 --- a/apps/blunt_absinthe/test/support/update_person.ex +++ b/apps/blunt_absinthe/test/support/update_person.ex @@ -5,4 +5,5 @@ defmodule Blunt.Absinthe.Test.UpdatePerson do field :id, :binary_id field :name, :string field :gender, :enum, values: Person.genders(), required: false + field :address, Blunt.Absinthe.Test.Address, required: false end From 1407660e21dfb7121dec21e82d848e3c75bb03ec Mon Sep 17 00:00:00 2001 From: Matthew Boehlig Date: Wed, 5 Oct 2022 13:36:53 -0500 Subject: [PATCH 04/10] apparently same error message when using input object --- .../test/blunt/absinthe/mutation_test.exs | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/apps/blunt_absinthe/test/blunt/absinthe/mutation_test.exs b/apps/blunt_absinthe/test/blunt/absinthe/mutation_test.exs index b58239d..ea01e57 100644 --- a/apps/blunt_absinthe/test/blunt/absinthe/mutation_test.exs +++ b/apps/blunt_absinthe/test/blunt/absinthe/mutation_test.exs @@ -20,6 +20,19 @@ defmodule Blunt.Absinthe.MutationTest do } } } + """, + update_query: """ + mutation update($input: UpdatePersonInput){ + updatePerson(input: $input){ + id + name + gender + address { + line1 + line2 + } + } + } """ } end @@ -110,4 +123,20 @@ defmodule Blunt.Absinthe.MutationTest do assert %Object{fields: fields} = Absinthe.Schema.lookup_type(Schema, :dog) assert %{name: %{type: :string}} = fields end + + test "returns errors from deeper nested changeset validations", %{update_query: update_query} do + assert {:ok, %{errors: [%{message: message}]}} = + Absinthe.run(update_query, Schema, + variables: %{ + "input" => %{ + "id" => UUID.uuid4(), + "name" => "chris", + "gender" => "MALE", + "address" => %{"line1" => "--"} + } + } + ) + + assert message =~ "address.line1 should be at least 3 character(s)" + end end From 20885e4b11b80c05b68909515c388c9fd7b7e94d Mon Sep 17 00:00:00 2001 From: Matthew Boehlig Date: Wed, 5 Oct 2022 14:34:13 -0500 Subject: [PATCH 05/10] test: fix GetPerson binding --- apps/blunt_absinthe/test/support/get_person.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/blunt_absinthe/test/support/get_person.ex b/apps/blunt_absinthe/test/support/get_person.ex index 38915c4..1603491 100644 --- a/apps/blunt_absinthe/test/support/get_person.ex +++ b/apps/blunt_absinthe/test/support/get_person.ex @@ -9,5 +9,5 @@ defmodule Blunt.Absinthe.Test.GetPerson do field :error_out, :boolean, default: false - binding :person, BluntBoundedContext.QueryTest.ReadModel.Person + binding :person, Blunt.Absinthe.Test.ReadModel.Person end From 27612a00fabad94b262c3f8e8579ce2dda224bf2 Mon Sep 17 00:00:00 2001 From: Matthew Boehlig Date: Thu, 6 Oct 2022 15:24:38 -0500 Subject: [PATCH 06/10] join multiple errors on a field with ', ' --- apps/blunt_absinthe/lib/blunt/absinthe/absinthe_errors.ex | 2 +- apps/blunt_absinthe/test/blunt/absinthe/mutation_test.exs | 4 ++-- apps/blunt_absinthe/test/support/address.ex | 1 + 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/apps/blunt_absinthe/lib/blunt/absinthe/absinthe_errors.ex b/apps/blunt_absinthe/lib/blunt/absinthe/absinthe_errors.ex index 3295118..a9c9808 100644 --- a/apps/blunt_absinthe/lib/blunt/absinthe/absinthe_errors.ex +++ b/apps/blunt_absinthe/lib/blunt/absinthe/absinthe_errors.ex @@ -29,7 +29,7 @@ defmodule Blunt.Absinthe.AbsintheErrors do {key, messages}, acc when is_list(messages) or is_map(messages) -> Enum.map(messages, fn {field, messages} -> - [message: "#{key}.#{field} #{messages |> Enum.join(",")}"] + [message: "#{key}.#{field} #{messages |> Enum.join(", ")}"] message -> [message: "#{key} #{message}"] diff --git a/apps/blunt_absinthe/test/blunt/absinthe/mutation_test.exs b/apps/blunt_absinthe/test/blunt/absinthe/mutation_test.exs index ea01e57..0ebcedc 100644 --- a/apps/blunt_absinthe/test/blunt/absinthe/mutation_test.exs +++ b/apps/blunt_absinthe/test/blunt/absinthe/mutation_test.exs @@ -85,7 +85,7 @@ defmodule Blunt.Absinthe.MutationTest do variables: %{ "name" => "chris", "gender" => "MALE", - "address" => %{"line1" => "--"} + "address" => %{"line1" => "10"} } ) @@ -137,6 +137,6 @@ defmodule Blunt.Absinthe.MutationTest do } ) - assert message =~ "address.line1 should be at least 3 character(s)" + assert message =~ "address.line1 should start with a number, should be at least 3 character(s)" end end diff --git a/apps/blunt_absinthe/test/support/address.ex b/apps/blunt_absinthe/test/support/address.ex index e2901d5..5550b7b 100644 --- a/apps/blunt_absinthe/test/support/address.ex +++ b/apps/blunt_absinthe/test/support/address.ex @@ -9,5 +9,6 @@ defmodule Blunt.Absinthe.Test.Address do def handle_validate(changeset, _opts) do changeset |> validate_length(:line1, min: 3) + |> validate_format(:line1, ~r/(\d)+.*/, message: "should start with a number") end end From 3d408d3ee3db9d28c14f3b2bd34395178c074643 Mon Sep 17 00:00:00 2001 From: Matthew Boehlig Date: Fri, 7 Oct 2022 15:47:19 -0500 Subject: [PATCH 07/10] absinthe: handle deeply nested validation errors - flatten to leaf values with key path --- .../lib/blunt/absinthe/absinthe_errors.ex | 17 +++++-- .../test/blunt/absinthe/mutation_test.exs | 48 +++++++++++++++++++ 2 files changed, 60 insertions(+), 5 deletions(-) diff --git a/apps/blunt_absinthe/lib/blunt/absinthe/absinthe_errors.ex b/apps/blunt_absinthe/lib/blunt/absinthe/absinthe_errors.ex index a9c9808..047ad7b 100644 --- a/apps/blunt_absinthe/lib/blunt/absinthe/absinthe_errors.ex +++ b/apps/blunt_absinthe/lib/blunt/absinthe/absinthe_errors.ex @@ -27,12 +27,12 @@ defmodule Blunt.Absinthe.AbsintheErrors do Enum.map(messages, fn message -> [message: message] end) ++ acc {key, messages}, acc when is_list(messages) or is_map(messages) -> - Enum.map(messages, fn - {field, messages} -> - [message: "#{key}.#{field} #{messages |> Enum.join(", ")}"] + Enum.flat_map(leaves_with_path(messages, [key]), fn + {path, messages} -> + label = Enum.join(path, ".") + message = messages |> List.wrap() |> Enum.join(", ") - message -> - [message: "#{key} #{message}"] + [[message: "#{label} #{message}"]] end) ++ acc {key, message}, acc when is_binary(message) -> @@ -40,4 +40,11 @@ defmodule Blunt.Absinthe.AbsintheErrors do end) |> Enum.map(&Keyword.merge(&1, extra_properties)) end + + def leaves_with_path(input, path \\ []) do + Enum.flat_map(input, fn {key, value} -> + full = [key | path] + if is_map(value), do: leaves_with_path(value, full), else: [{Enum.reverse(full), value}] + end) + end end diff --git a/apps/blunt_absinthe/test/blunt/absinthe/mutation_test.exs b/apps/blunt_absinthe/test/blunt/absinthe/mutation_test.exs index 0ebcedc..bc5902d 100644 --- a/apps/blunt_absinthe/test/blunt/absinthe/mutation_test.exs +++ b/apps/blunt_absinthe/test/blunt/absinthe/mutation_test.exs @@ -139,4 +139,52 @@ defmodule Blunt.Absinthe.MutationTest do assert message =~ "address.line1 should start with a number, should be at least 3 character(s)" end + + test "reduce_map" do + errors = %{ + input: %{ + person: %{ + address: %{ + stuff: %{ + thing: "broken" + }, + other: "fixed", + city: "unknown" + }, + tree: ["trunk", "branches"] + } + } + } + + assert [ + {[:input, :person, :address, :city], "unknown"}, + {[:input, :person, :address, :other], "fixed"}, + {[:input, :person, :address, :stuff, :thing], "broken"}, + {[:input, :person, :tree], ["trunk", "branches"]} + ] = Blunt.Absinthe.AbsintheErrors.leaves_with_path(errors) + end + + test "recur" do + tree = %{"name" => "chris", "gender" => "MALE", "address" => %{"line1" => "42 Infinity Ave", "line2" => nil}} + + assert [ + {["address", "line1"], "42 Infinity Ave"}, + {["address", "line2"], nil}, + {["gender"], "MALE"}, + {["name"], "chris"} + ] = Blunt.Absinthe.AbsintheErrors.leaves_with_path(tree) + end + + test "format errors with key in message" do + errors = %{input: %{person: %{address: %{stuff: %{thing: "everything is b0rked"}}}}} + + assert [{[:input, :person, :address, :stuff, :thing], "everything is b0rked"}] = + Blunt.Absinthe.AbsintheErrors.leaves_with_path(errors) + + result = Blunt.Absinthe.AbsintheErrors.format(errors, dispatch_id: "23424234") + + assert result == [ + [message: "input.person.address.stuff.thing everything is b0rked", dispatch_id: "23424234"] + ] + end end From 53e7034839ebcbe278f2dce9a18d29cdfb3bef83 Mon Sep 17 00:00:00 2001 From: Matthew Boehlig Date: Fri, 7 Oct 2022 17:51:03 -0500 Subject: [PATCH 08/10] absinthe: errors include path to field --- .../lib/blunt/absinthe/absinthe_errors.ex | 5 +- .../test/blunt/absinthe/mutation_test.exs | 64 ++++++++----------- 2 files changed, 31 insertions(+), 38 deletions(-) diff --git a/apps/blunt_absinthe/lib/blunt/absinthe/absinthe_errors.ex b/apps/blunt_absinthe/lib/blunt/absinthe/absinthe_errors.ex index 047ad7b..5f9910f 100644 --- a/apps/blunt_absinthe/lib/blunt/absinthe/absinthe_errors.ex +++ b/apps/blunt_absinthe/lib/blunt/absinthe/absinthe_errors.ex @@ -29,14 +29,15 @@ defmodule Blunt.Absinthe.AbsintheErrors do {key, messages}, acc when is_list(messages) or is_map(messages) -> Enum.flat_map(leaves_with_path(messages, [key]), fn {path, messages} -> + path = Enum.map(path, &to_string/1) label = Enum.join(path, ".") message = messages |> List.wrap() |> Enum.join(", ") - [[message: "#{label} #{message}"]] + [[message: "#{label} #{message}", path: path]] end) ++ acc {key, message}, acc when is_binary(message) -> - [[message: "#{key} #{message}"] | acc] + [[message: "#{key} #{message}", path: [key]] | acc] end) |> Enum.map(&Keyword.merge(&1, extra_properties)) end diff --git a/apps/blunt_absinthe/test/blunt/absinthe/mutation_test.exs b/apps/blunt_absinthe/test/blunt/absinthe/mutation_test.exs index bc5902d..222830c 100644 --- a/apps/blunt_absinthe/test/blunt/absinthe/mutation_test.exs +++ b/apps/blunt_absinthe/test/blunt/absinthe/mutation_test.exs @@ -80,7 +80,7 @@ defmodule Blunt.Absinthe.MutationTest do end test "returns errors from nested changeset validations", %{query: query} do - assert {:ok, %{errors: [%{message: message}]}} = + assert {:ok, %{errors: [%{message: message, path: path}]}} = Absinthe.run(query, Schema, variables: %{ "name" => "chris", @@ -90,6 +90,7 @@ defmodule Blunt.Absinthe.MutationTest do ) assert message =~ "address.line1 should be at least 3 character(s)" + assert path == ~w(createPerson address line1) end test "user is put in the context from absinthe resolution context", %{query: query} do @@ -125,7 +126,7 @@ defmodule Blunt.Absinthe.MutationTest do end test "returns errors from deeper nested changeset validations", %{update_query: update_query} do - assert {:ok, %{errors: [%{message: message}]}} = + assert {:ok, %{errors: [%{message: message, path: path}]}} = Absinthe.run(update_query, Schema, variables: %{ "input" => %{ @@ -138,53 +139,44 @@ defmodule Blunt.Absinthe.MutationTest do ) assert message =~ "address.line1 should start with a number, should be at least 3 character(s)" + assert path == ~w(updatePerson input address line1) end - test "reduce_map" do + test "paths" do errors = %{ - input: %{ - person: %{ - address: %{ - stuff: %{ - thing: "broken" - }, - other: "fixed", - city: "unknown" + a: %{ + b: %{ + c: %{ + d: "broken" }, - tree: ["trunk", "branches"] - } + cc: "fixed", + ccc: "unknown" + }, + tree: ["trunk", "branches"] } } - assert [ - {[:input, :person, :address, :city], "unknown"}, - {[:input, :person, :address, :other], "fixed"}, - {[:input, :person, :address, :stuff, :thing], "broken"}, - {[:input, :person, :tree], ["trunk", "branches"]} - ] = Blunt.Absinthe.AbsintheErrors.leaves_with_path(errors) - end - - test "recur" do - tree = %{"name" => "chris", "gender" => "MALE", "address" => %{"line1" => "42 Infinity Ave", "line2" => nil}} + {:ok, context} = DispatchContext.new(%Blunt.Absinthe.Test.CreatePerson{}, []) + context = DispatchContext.put_error(context, errors) + %{id: dispatch_id} = context assert [ - {["address", "line1"], "42 Infinity Ave"}, - {["address", "line2"], nil}, - {["gender"], "MALE"}, - {["name"], "chris"} - ] = Blunt.Absinthe.AbsintheErrors.leaves_with_path(tree) + [message: "a.b.c.d broken", path: ~w(a b c d), dispatch_id: ^dispatch_id], + [message: "a.b.cc fixed", path: ~w(a b cc), dispatch_id: ^dispatch_id], + [message: "a.b.ccc unknown", path: ~w(a b ccc), dispatch_id: ^dispatch_id], + [message: "a.tree trunk, branches", path: ~w(a tree), dispatch_id: ^dispatch_id] + ] = Blunt.Absinthe.AbsintheErrors.from_dispatch_context(context) end test "format errors with key in message" do errors = %{input: %{person: %{address: %{stuff: %{thing: "everything is b0rked"}}}}} - assert [{[:input, :person, :address, :stuff, :thing], "everything is b0rked"}] = - Blunt.Absinthe.AbsintheErrors.leaves_with_path(errors) - - result = Blunt.Absinthe.AbsintheErrors.format(errors, dispatch_id: "23424234") - - assert result == [ - [message: "input.person.address.stuff.thing everything is b0rked", dispatch_id: "23424234"] - ] + assert [ + [ + message: "input.person.address.stuff.thing everything is b0rked", + path: ~w( input person address stuff thing ), + dispatch_id: "23424234" + ] + ] = Blunt.Absinthe.AbsintheErrors.format(errors, dispatch_id: "23424234") end end From c044172de9aed45a130deab53e927a26906695e5 Mon Sep 17 00:00:00 2001 From: Matthew Boehlig Date: Sat, 8 Oct 2022 01:32:26 -0500 Subject: [PATCH 09/10] fixup! absinthe: handle deeply nested validation errors --- apps/blunt_absinthe/lib/blunt/absinthe/absinthe_errors.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/blunt_absinthe/lib/blunt/absinthe/absinthe_errors.ex b/apps/blunt_absinthe/lib/blunt/absinthe/absinthe_errors.ex index 5f9910f..02f7a2c 100644 --- a/apps/blunt_absinthe/lib/blunt/absinthe/absinthe_errors.ex +++ b/apps/blunt_absinthe/lib/blunt/absinthe/absinthe_errors.ex @@ -42,7 +42,7 @@ defmodule Blunt.Absinthe.AbsintheErrors do |> Enum.map(&Keyword.merge(&1, extra_properties)) end - def leaves_with_path(input, path \\ []) do + defp leaves_with_path(input, path \\ []) do Enum.flat_map(input, fn {key, value} -> full = [key | path] if is_map(value), do: leaves_with_path(value, full), else: [{Enum.reverse(full), value}] From ea17ec620f451a7da44373dc5c18c02ec9445cf2 Mon Sep 17 00:00:00 2001 From: Matthew Boehlig Date: Sat, 8 Oct 2022 02:17:12 -0500 Subject: [PATCH 10/10] absinthe: wrap validation errors in input key if input_object --- apps/blunt_absinthe/lib/blunt/absinthe/field.ex | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/apps/blunt_absinthe/lib/blunt/absinthe/field.ex b/apps/blunt_absinthe/lib/blunt/absinthe/field.ex index 25c180b..9fb2ec5 100644 --- a/apps/blunt_absinthe/lib/blunt/absinthe/field.ex +++ b/apps/blunt_absinthe/lib/blunt/absinthe/field.ex @@ -86,6 +86,8 @@ defmodule Blunt.Absinthe.Field do def dispatch_and_resolve(operation, message_module, query_opts, parent, args, resolution) do context_configuration = DispatchContextConfiguration.configure(message_module, resolution) + input_object = Keyword.get(query_opts, :input_object, false) || Keyword.get(query_opts, :input_object?, false) + args = Map.get(args, :input, args) opts = @@ -112,6 +114,8 @@ defmodule Blunt.Absinthe.Field do return_value {:error, errors} when is_map(errors) -> + errors = if input_object, do: %{input: errors}, else: errors + {:error, AbsintheErrors.format(errors)} {:ok, %Context{} = context} ->