From 00b6c046987545340d02f801b0f9e46394141aab Mon Sep 17 00:00:00 2001 From: tamanugi Date: Fri, 4 Aug 2023 00:15:28 +0900 Subject: [PATCH 01/36] fix: non-null-list-of-non-null error when root data is nil --- .../phase/document/execution/resolution.ex | 2 +- .../phase/execution/non_null_test.exs | 34 +++++++++++++++++-- 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/lib/absinthe/phase/document/execution/resolution.ex b/lib/absinthe/phase/document/execution/resolution.ex index 67df033ebd..c6aefa1090 100644 --- a/lib/absinthe/phase/document/execution/resolution.ex +++ b/lib/absinthe/phase/document/execution/resolution.ex @@ -285,7 +285,7 @@ defmodule Absinthe.Phase.Document.Execution.Resolution do |> propagate_null_trimming end - defp maybe_add_non_null_error([], values, %Type.NonNull{of_type: %Type.List{}}) do + defp maybe_add_non_null_error([], values, %Type.NonNull{of_type: %Type.List{}}) when is_list(values) do values |> Enum.with_index() |> Enum.filter(&is_nil(elem(&1, 0))) diff --git a/test/absinthe/phase/execution/non_null_test.exs b/test/absinthe/phase/execution/non_null_test.exs index 9d62e41fd0..ca7dc85bd1 100644 --- a/test/absinthe/phase/execution/non_null_test.exs +++ b/test/absinthe/phase/execution/non_null_test.exs @@ -18,6 +18,14 @@ defmodule Absinthe.Phase.Document.Execution.NonNullTest do defp things_resolver(_, %{make_null: make_null}, _) do if make_null do + {:ok, nil} + else + {:ok, [%{}]} + end + end + + defp things_resolver(_, %{make_child_null: make_child_null}, _) do + if make_child_null do {:ok, [nil]} else {:ok, [%{}]} @@ -53,6 +61,7 @@ defmodule Absinthe.Phase.Document.Execution.NonNullTest do field :non_null_list_of_non_null, non_null(list_of(non_null(:thing))) do arg :make_null, :boolean + arg :make_child_null, :boolean resolve &things_resolver/3 end end @@ -79,6 +88,7 @@ defmodule Absinthe.Phase.Document.Execution.NonNullTest do field :non_null_list_of_non_null, non_null(list_of(non_null(:thing))) do arg :make_null, :boolean + arg :make_child_null, :boolean resolve &things_resolver/3 end @@ -263,7 +273,7 @@ defmodule Absinthe.Phase.Document.Execution.NonNullTest do assert {:ok, %{data: data, errors: errors}} == Absinthe.run(doc, Schema) end - test "list of non null things works when child is null" do + test "list of non null things works when root is null" do doc = """ { nonNullListOfNonNull(makeNull: true) { __typename } @@ -272,6 +282,26 @@ defmodule Absinthe.Phase.Document.Execution.NonNullTest do data = nil + errors = [ + %{ + locations: [%{column: 3, line: 2}], + message: "Cannot return null for non-nullable field", + path: ["nonNullListOfNonNull"] + } + ] + + assert {:ok, %{data: data, errors: errors}} == Absinthe.run(doc, Schema) + end + + test "list of non null things works when child is null" do + doc = """ + { + nonNullListOfNonNull(makeChildNull: true) { __typename } + } + """ + + data = nil + errors = [ %{ locations: [%{column: 3, line: 2}], @@ -290,7 +320,7 @@ defmodule Absinthe.Phase.Document.Execution.NonNullTest do nonNullListOfNonNull { nonNullListOfNonNull { nonNullListOfNonNull { - nonNullListOfNonNull(makeNull: true) { __typename } + nonNullListOfNonNull(makeChildNull: true) { __typename } } } } From f087c6f9a5453c886b745eaff62cb8b39b7ce596 Mon Sep 17 00:00:00 2001 From: tamanugi Date: Fri, 4 Aug 2023 00:42:46 +0900 Subject: [PATCH 02/36] lint: formatted --- lib/absinthe/phase/document/execution/resolution.ex | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/absinthe/phase/document/execution/resolution.ex b/lib/absinthe/phase/document/execution/resolution.ex index c6aefa1090..766d9d27e8 100644 --- a/lib/absinthe/phase/document/execution/resolution.ex +++ b/lib/absinthe/phase/document/execution/resolution.ex @@ -285,7 +285,8 @@ defmodule Absinthe.Phase.Document.Execution.Resolution do |> propagate_null_trimming end - defp maybe_add_non_null_error([], values, %Type.NonNull{of_type: %Type.List{}}) when is_list(values) do + defp maybe_add_non_null_error([], values, %Type.NonNull{of_type: %Type.List{}}) + when is_list(values) do values |> Enum.with_index() |> Enum.filter(&is_nil(elem(&1, 0))) From 556a8cc9a4d6a5199d10477cd1674e67d8c3cc03 Mon Sep 17 00:00:00 2001 From: tamanugi Date: Sun, 6 Aug 2023 00:31:31 +0900 Subject: [PATCH 03/36] fix: testing for non-null-list-of-nullable --- test/absinthe/phase/execution/non_null_test.exs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/test/absinthe/phase/execution/non_null_test.exs b/test/absinthe/phase/execution/non_null_test.exs index ca7dc85bd1..193ad9d367 100644 --- a/test/absinthe/phase/execution/non_null_test.exs +++ b/test/absinthe/phase/execution/non_null_test.exs @@ -86,8 +86,12 @@ defmodule Absinthe.Phase.Document.Execution.NonNullTest do resolve &things_resolver/3 end - field :non_null_list_of_non_null, non_null(list_of(non_null(:thing))) do + field :non_null_list_of_nullable, non_null(list_of(:thing)) do arg :make_null, :boolean + resolve &things_resolver/3 + end + + field :non_null_list_of_non_null, non_null(list_of(non_null(:thing))) do arg :make_child_null, :boolean resolve &things_resolver/3 end @@ -273,10 +277,10 @@ defmodule Absinthe.Phase.Document.Execution.NonNullTest do assert {:ok, %{data: data, errors: errors}} == Absinthe.run(doc, Schema) end - test "list of non null things works when root is null" do + test "list of non null works when root is null" do doc = """ { - nonNullListOfNonNull(makeNull: true) { __typename } + nonNullListOfNullable(makeNull: true) { __typename } } """ @@ -286,7 +290,7 @@ defmodule Absinthe.Phase.Document.Execution.NonNullTest do %{ locations: [%{column: 3, line: 2}], message: "Cannot return null for non-nullable field", - path: ["nonNullListOfNonNull"] + path: ["nonNullListOfNullable"] } ] From f02ead8d5f50b046af6766d53b8839ec9ac1192a Mon Sep 17 00:00:00 2001 From: Ben Wilson Date: Sat, 5 Aug 2023 16:44:06 -0400 Subject: [PATCH 04/36] revert unnecessary test changes. --- .../phase/execution/non_null_test.exs | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/test/absinthe/phase/execution/non_null_test.exs b/test/absinthe/phase/execution/non_null_test.exs index 193ad9d367..f61af1dfc1 100644 --- a/test/absinthe/phase/execution/non_null_test.exs +++ b/test/absinthe/phase/execution/non_null_test.exs @@ -24,8 +24,8 @@ defmodule Absinthe.Phase.Document.Execution.NonNullTest do end end - defp things_resolver(_, %{make_child_null: make_child_null}, _) do - if make_child_null do + defp things_resolver(_, %{make_element_null: make_element_null}, _) do + if make_element_null do {:ok, [nil]} else {:ok, [%{}]} @@ -61,7 +61,7 @@ defmodule Absinthe.Phase.Document.Execution.NonNullTest do field :non_null_list_of_non_null, non_null(list_of(non_null(:thing))) do arg :make_null, :boolean - arg :make_child_null, :boolean + arg :make_element_null, :boolean resolve &things_resolver/3 end end @@ -86,13 +86,14 @@ defmodule Absinthe.Phase.Document.Execution.NonNullTest do resolve &things_resolver/3 end - field :non_null_list_of_nullable, non_null(list_of(:thing)) do + field :non_null_list_of_non_null, non_null(list_of(non_null(:thing))) do arg :make_null, :boolean resolve &things_resolver/3 end - field :non_null_list_of_non_null, non_null(list_of(non_null(:thing))) do - arg :make_child_null, :boolean + field :non_null_list_of_nullable, non_null(list_of(:thing)) do + arg :make_null, :boolean + arg :make_element_null, :boolean resolve &things_resolver/3 end @@ -139,7 +140,7 @@ defmodule Absinthe.Phase.Document.Execution.NonNullTest do assert {:ok, %{data: data, errors: errors}} == Absinthe.run(doc, Schema) end - test "returning nil from a non null child of non nulls pushes nil all the way up to data" do + test "returning nil from a non null child of non nulls pushes nil all the way up to data"" do doc = """ { nonNull { nonNull { nonNull(makeNull: true) { __typename }}} @@ -300,7 +301,7 @@ defmodule Absinthe.Phase.Document.Execution.NonNullTest do test "list of non null things works when child is null" do doc = """ { - nonNullListOfNonNull(makeChildNull: true) { __typename } + nonNullListOfNonNull(makeNull: true) { __typename } } """ @@ -324,7 +325,7 @@ defmodule Absinthe.Phase.Document.Execution.NonNullTest do nonNullListOfNonNull { nonNullListOfNonNull { nonNullListOfNonNull { - nonNullListOfNonNull(makeChildNull: true) { __typename } + nonNullListOfNonNull(makeNull: true) { __typename } } } } From d7c36df738a087ea3da858a913b4231f42891177 Mon Sep 17 00:00:00 2001 From: Ben Wilson Date: Sat, 5 Aug 2023 16:45:02 -0400 Subject: [PATCH 05/36] typo fix --- test/absinthe/phase/execution/non_null_test.exs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/absinthe/phase/execution/non_null_test.exs b/test/absinthe/phase/execution/non_null_test.exs index f61af1dfc1..b5268a9586 100644 --- a/test/absinthe/phase/execution/non_null_test.exs +++ b/test/absinthe/phase/execution/non_null_test.exs @@ -140,7 +140,7 @@ defmodule Absinthe.Phase.Document.Execution.NonNullTest do assert {:ok, %{data: data, errors: errors}} == Absinthe.run(doc, Schema) end - test "returning nil from a non null child of non nulls pushes nil all the way up to data"" do + test "returning nil from a non null child of non nulls pushes nil all the way up to data" do doc = """ { nonNull { nonNull { nonNull(makeNull: true) { __typename }}} From 150eb3d249120dc3b71f1877d6a77277ca972177 Mon Sep 17 00:00:00 2001 From: Ben Wilson Date: Sat, 5 Aug 2023 16:56:02 -0400 Subject: [PATCH 06/36] reset tests and focus on rewording in prep for clear test additions --- .../phase/execution/non_null_test.exs | 75 +++++-------------- 1 file changed, 20 insertions(+), 55 deletions(-) diff --git a/test/absinthe/phase/execution/non_null_test.exs b/test/absinthe/phase/execution/non_null_test.exs index b5268a9586..f1157d7e60 100644 --- a/test/absinthe/phase/execution/non_null_test.exs +++ b/test/absinthe/phase/execution/non_null_test.exs @@ -4,8 +4,8 @@ defmodule Absinthe.Phase.Document.Execution.NonNullTest do defmodule Schema do use Absinthe.Schema - defp thing_resolver(_, %{make_null: make_null}, _) do - if make_null do + defp thing_resolver(_, %{return_null: return_null}, _) do + if return_null do {:ok, nil} else {:ok, %{}} @@ -16,16 +16,8 @@ defmodule Absinthe.Phase.Document.Execution.NonNullTest do {:ok, %{}} end - defp things_resolver(_, %{make_null: make_null}, _) do - if make_null do - {:ok, nil} - else - {:ok, [%{}]} - end - end - - defp things_resolver(_, %{make_element_null: make_element_null}, _) do - if make_element_null do + defp things_resolver(_, %{return_null_element: return_null_element}, _) do + if return_null_element do {:ok, [nil]} else {:ok, [%{}]} @@ -38,7 +30,7 @@ defmodule Absinthe.Phase.Document.Execution.NonNullTest do object :thing do field :nullable, :thing do - arg :make_null, :boolean + arg :return_null, :boolean resolve &thing_resolver/3 end @@ -49,7 +41,7 @@ defmodule Absinthe.Phase.Document.Execution.NonNullTest do testing the null handling behaviour. """ field :non_null, non_null(:thing) do - arg :make_null, :boolean + arg :return_null, :boolean resolve &thing_resolver/3 end @@ -60,15 +52,14 @@ defmodule Absinthe.Phase.Document.Execution.NonNullTest do end field :non_null_list_of_non_null, non_null(list_of(non_null(:thing))) do - arg :make_null, :boolean - arg :make_element_null, :boolean + arg :return_null_element, :boolean resolve &things_resolver/3 end end query do field :nullable, :thing do - arg :make_null, :boolean + arg :return_null, :boolean resolve &thing_resolver/3 end @@ -87,13 +78,7 @@ defmodule Absinthe.Phase.Document.Execution.NonNullTest do end field :non_null_list_of_non_null, non_null(list_of(non_null(:thing))) do - arg :make_null, :boolean - resolve &things_resolver/3 - end - - field :non_null_list_of_nullable, non_null(list_of(:thing)) do - arg :make_null, :boolean - arg :make_element_null, :boolean + arg :return_null_element, :boolean resolve &things_resolver/3 end @@ -104,7 +89,7 @@ defmodule Absinthe.Phase.Document.Execution.NonNullTest do testing the null handling behaviour. """ field :non_null, non_null(:thing) do - arg :make_null, :boolean + arg :return_null, :boolean resolve &thing_resolver/3 end end @@ -113,7 +98,7 @@ defmodule Absinthe.Phase.Document.Execution.NonNullTest do test "getting a null value normally works fine" do doc = """ { - nullable { nullable(makeNull: true) { __typename }} + nullable { nullable(returnNull: true) { __typename }} } """ @@ -123,7 +108,7 @@ defmodule Absinthe.Phase.Document.Execution.NonNullTest do test "returning nil from a non null field makes the parent nullable null" do doc = """ { - nullable { nullable { nonNull(makeNull: true) { __typename }}} + nullable { nullable { nonNull(returnNull: true) { __typename }}} } """ @@ -143,7 +128,7 @@ defmodule Absinthe.Phase.Document.Execution.NonNullTest do test "returning nil from a non null child of non nulls pushes nil all the way up to data" do doc = """ { - nonNull { nonNull { nonNull(makeNull: true) { __typename }}} + nonNull { nonNull { nonNull(returnNull: true) { __typename }}} } """ @@ -221,7 +206,7 @@ defmodule Absinthe.Phase.Document.Execution.NonNullTest do test "list of nullable things works when child has a null violation" do doc = """ { - nullableListOfNullable { nonNull(makeNull: true) { __typename } } + nullableListOfNullable { nonNull(returnNull: true) { __typename } } } """ @@ -238,10 +223,10 @@ defmodule Absinthe.Phase.Document.Execution.NonNullTest do assert {:ok, %{data: data, errors: errors}} == Absinthe.run(doc, Schema) end - test "list of non null things works when child has a null violation" do + test "list of non null things returns an error when child has a null violation" do doc = """ { - nullableListOfNonNull { nonNull(makeNull: true) { __typename } } + nullableListOfNonNull { nonNull(returnNull: true) { __typename } } } """ @@ -258,10 +243,10 @@ defmodule Absinthe.Phase.Document.Execution.NonNullTest do assert {:ok, %{data: data, errors: errors}} == Absinthe.run(doc, Schema) end - test "list of non null things works when child has a null violation and the root field is non null" do + test "list of non null things returns an error when child has a null violation and the root field is non null" do doc = """ { - nonNullListOfNonNull { nonNull(makeNull: true) { __typename } } + nonNullListOfNonNull { nonNull(returnNull: true) { __typename } } } """ @@ -278,30 +263,10 @@ defmodule Absinthe.Phase.Document.Execution.NonNullTest do assert {:ok, %{data: data, errors: errors}} == Absinthe.run(doc, Schema) end - test "list of non null works when root is null" do - doc = """ - { - nonNullListOfNullable(makeNull: true) { __typename } - } - """ - - data = nil - - errors = [ - %{ - locations: [%{column: 3, line: 2}], - message: "Cannot return null for non-nullable field", - path: ["nonNullListOfNullable"] - } - ] - - assert {:ok, %{data: data, errors: errors}} == Absinthe.run(doc, Schema) - end - test "list of non null things works when child is null" do doc = """ { - nonNullListOfNonNull(makeNull: true) { __typename } + nonNullListOfNonNull(returnNullElement: true) { __typename } } """ @@ -325,7 +290,7 @@ defmodule Absinthe.Phase.Document.Execution.NonNullTest do nonNullListOfNonNull { nonNullListOfNonNull { nonNullListOfNonNull { - nonNullListOfNonNull(makeNull: true) { __typename } + nonNullListOfNonNull(returnNullElement: true) { __typename } } } } From e9c85562445c442c971ea0624a627b4569a72993 Mon Sep 17 00:00:00 2001 From: Ben Wilson Date: Sat, 5 Aug 2023 16:59:38 -0400 Subject: [PATCH 07/36] continued wording tweaks --- test/absinthe/phase/execution/non_null_test.exs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/test/absinthe/phase/execution/non_null_test.exs b/test/absinthe/phase/execution/non_null_test.exs index f1157d7e60..3e6d681bb5 100644 --- a/test/absinthe/phase/execution/non_null_test.exs +++ b/test/absinthe/phase/execution/non_null_test.exs @@ -105,7 +105,7 @@ defmodule Absinthe.Phase.Document.Execution.NonNullTest do assert {:ok, %{data: %{"nullable" => %{"nullable" => nil}}}} == Absinthe.run(doc, Schema) end - test "returning nil from a non null field makes the parent nullable null" do + test "returning nil from a non null field returns an error and makes the parent nullable null" do doc = """ { nullable { nullable { nonNull(returnNull: true) { __typename }}} @@ -125,7 +125,7 @@ defmodule Absinthe.Phase.Document.Execution.NonNullTest do assert {:ok, %{data: data, errors: errors}} == Absinthe.run(doc, Schema) end - test "returning nil from a non null child of non nulls pushes nil all the way up to data" do + test "returning nil from a non null child of non nulls returns an error and pushes nil all the way up to data" do doc = """ { nonNull { nonNull { nonNull(returnNull: true) { __typename }}} @@ -145,7 +145,7 @@ defmodule Absinthe.Phase.Document.Execution.NonNullTest do assert {:ok, %{data: data, errors: errors}} == Absinthe.run(doc, Schema) end - test "error propagation to root field returns nil on data" do + test "returning an error from a non null field makes the parent nullable null" do doc = """ { nullable { nullable { nonNullErrorField }} @@ -165,7 +165,7 @@ defmodule Absinthe.Phase.Document.Execution.NonNullTest do assert {:ok, %{data: data, errors: errors}} == Absinthe.run(doc, Schema) end - test "returning an error from a non null field makes the parent nullable null" do + test "error propagation to root field returns nil on data" do doc = """ { nonNull { nonNull { nonNullErrorField }} @@ -203,7 +203,7 @@ defmodule Absinthe.Phase.Document.Execution.NonNullTest do end describe "lists" do - test "list of nullable things works when child has a null violation" do + test "list of nullable things returns an error when child has a null violation" do doc = """ { nullableListOfNullable { nonNull(returnNull: true) { __typename } } @@ -263,7 +263,7 @@ defmodule Absinthe.Phase.Document.Execution.NonNullTest do assert {:ok, %{data: data, errors: errors}} == Absinthe.run(doc, Schema) end - test "list of non null things works when child is null" do + test "list of non null things returns an error when child is null" do doc = """ { nonNullListOfNonNull(returnNullElement: true) { __typename } From 520eb56a1b1b78d8912e4f95ed97a6eba7328fc2 Mon Sep 17 00:00:00 2001 From: Ben Wilson Date: Sat, 5 Aug 2023 17:04:10 -0400 Subject: [PATCH 08/36] incorporate new test behavior --- .../phase/execution/non_null_test.exs | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/test/absinthe/phase/execution/non_null_test.exs b/test/absinthe/phase/execution/non_null_test.exs index 3e6d681bb5..7ef46f0cc3 100644 --- a/test/absinthe/phase/execution/non_null_test.exs +++ b/test/absinthe/phase/execution/non_null_test.exs @@ -24,6 +24,14 @@ defmodule Absinthe.Phase.Document.Execution.NonNullTest do end end + defp things_resolver(_, %{return_null: return_null}, _) do + if return_null do + {:ok, nil} + else + {:ok, [%{}]} + end + end + defp things_resolver(_, _, _) do {:ok, [%{}]} end @@ -82,6 +90,12 @@ defmodule Absinthe.Phase.Document.Execution.NonNullTest do resolve &things_resolver/3 end + field :non_null_list_of_nullable, non_null(list_of(:thing)) do + arg :return_null, :boolean + arg :return_null_element, :boolean + resolve &things_resolver/3 + end + @desc """ A field declared to be non null. @@ -263,6 +277,26 @@ defmodule Absinthe.Phase.Document.Execution.NonNullTest do assert {:ok, %{data: data, errors: errors}} == Absinthe.run(doc, Schema) end + test "non null list of non null returns an error when null" do + doc = """ + { + nonNullListOfNullable(returnNull: true) { __typename } + } + """ + + data = nil + + errors = [ + %{ + locations: [%{column: 3, line: 2}], + message: "Cannot return null for non-nullable field", + path: ["nonNullListOfNullable"] + } + ] + + assert {:ok, %{data: data, errors: errors}} == Absinthe.run(doc, Schema) + end + test "list of non null things returns an error when child is null" do doc = """ { From a8b7c14963ea430368c2a22f6b99b2de2285ad7c Mon Sep 17 00:00:00 2001 From: Ben Wilson Date: Sat, 5 Aug 2023 17:07:38 -0400 Subject: [PATCH 09/36] test name fix --- test/absinthe/phase/execution/non_null_test.exs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/absinthe/phase/execution/non_null_test.exs b/test/absinthe/phase/execution/non_null_test.exs index 7ef46f0cc3..6bc7e4d679 100644 --- a/test/absinthe/phase/execution/non_null_test.exs +++ b/test/absinthe/phase/execution/non_null_test.exs @@ -277,7 +277,7 @@ defmodule Absinthe.Phase.Document.Execution.NonNullTest do assert {:ok, %{data: data, errors: errors}} == Absinthe.run(doc, Schema) end - test "non null list of non null returns an error when null" do + test "non null list of nullable returns an error when null" do doc = """ { nonNullListOfNullable(returnNull: true) { __typename } From feb74ea558d1299fd67c62a72471b2f35a73811c Mon Sep 17 00:00:00 2001 From: Ben Wilson Date: Sat, 5 Aug 2023 17:07:54 -0400 Subject: [PATCH 10/36] show test that fails --- test/absinthe/phase/execution/non_null_test.exs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/test/absinthe/phase/execution/non_null_test.exs b/test/absinthe/phase/execution/non_null_test.exs index 6bc7e4d679..f65d401b03 100644 --- a/test/absinthe/phase/execution/non_null_test.exs +++ b/test/absinthe/phase/execution/non_null_test.exs @@ -297,6 +297,18 @@ defmodule Absinthe.Phase.Document.Execution.NonNullTest do assert {:ok, %{data: data, errors: errors}} == Absinthe.run(doc, Schema) end + test "non null list of nullable allows returning a list with null elements" do + doc = """ + { + nonNullListOfNullable(returnNullElement: true) { __typename } + } + """ + + data = %{"nonNullListOfNullable" => [nil]} + + assert {:ok, %{data: data}} == Absinthe.run(doc, Schema) + end + test "list of non null things returns an error when child is null" do doc = """ { From be62a77eb77a06f04bbf4a67a5b48c333ebd2949 Mon Sep 17 00:00:00 2001 From: Ben Wilson Date: Sat, 5 Aug 2023 17:21:51 -0400 Subject: [PATCH 11/36] allow nullable elements of nullable lists --- .../phase/document/execution/resolution.ex | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/lib/absinthe/phase/document/execution/resolution.ex b/lib/absinthe/phase/document/execution/resolution.ex index 766d9d27e8..f831a5d613 100644 --- a/lib/absinthe/phase/document/execution/resolution.ex +++ b/lib/absinthe/phase/document/execution/resolution.ex @@ -285,7 +285,20 @@ defmodule Absinthe.Phase.Document.Execution.Resolution do |> propagate_null_trimming end - defp maybe_add_non_null_error([], values, %Type.NonNull{of_type: %Type.List{}}) + defp maybe_add_non_null_error([], nil, %Type.NonNull{}) do + ["Cannot return null for non-nullable field"] + end + + # Unwrap the non null so we can check again for the possible case of a non null + # inside of a list. We want that clause to handle both + # - `non_null(list_of(non_null(:thing)))` + # - `list_of(non_null(:thing))` + # Thus the single layer of unwrapping here. + defp maybe_add_non_null_error([], values, %Type.NonNull{of_type: type}) do + maybe_add_non_null_error([], values, type) + end + + defp maybe_add_non_null_error([], values, %Type.List{of_type: %Type.NonNull{}}) when is_list(values) do values |> Enum.with_index() @@ -295,10 +308,6 @@ defmodule Absinthe.Phase.Document.Execution.Resolution do end) end - defp maybe_add_non_null_error([], nil, %Type.NonNull{}) do - ["Cannot return null for non-nullable field"] - end - defp maybe_add_non_null_error(errors, _, _) do errors end From 29db3e8d72a0afb9e500605c2fd4e5afb856844c Mon Sep 17 00:00:00 2001 From: Ben Wilson Date: Sat, 5 Aug 2023 17:24:36 -0400 Subject: [PATCH 12/36] changelog entry --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index dbaa240b92..42eb3b6972 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## Unreleased + +- Bugfix: [Handle non_null(list_of(:thing)) with null list elements properly](https://github.com/absinthe-graphql/absinthe/pull/1259) + ## 1.7.4 - Feature: Support Dataloader 2.0 From d0b724372bf29bc4bfc7bb6466bd6cb8a1fa7bfe Mon Sep 17 00:00:00 2001 From: Daniel Reigada Date: Fri, 11 Aug 2023 10:18:30 +0100 Subject: [PATCH 13/36] Add __private__ field to Absinthe.Type.Argument struct --- lib/absinthe/blueprint/schema/object_type_definition.ex | 4 +++- lib/absinthe/blueprint/schema/union_type_definition.ex | 4 +++- lib/absinthe/type/argument.ex | 8 ++++++-- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/lib/absinthe/blueprint/schema/object_type_definition.ex b/lib/absinthe/blueprint/schema/object_type_definition.ex index 8e7bb9d7bd..d62ec4c77f 100644 --- a/lib/absinthe/blueprint/schema/object_type_definition.ex +++ b/lib/absinthe/blueprint/schema/object_type_definition.ex @@ -84,7 +84,9 @@ defmodule Absinthe.Blueprint.Schema.ObjectTypeDefinition do description: arg_def.description, type: Blueprint.TypeReference.to_type(arg_def.type, schema), default_value: arg_def.default_value, - deprecation: arg_def.deprecation + deprecation: arg_def.deprecation, + __reference__: arg_def.__reference__, + __private__: arg_def.__private__ } {arg_def.identifier, arg} diff --git a/lib/absinthe/blueprint/schema/union_type_definition.ex b/lib/absinthe/blueprint/schema/union_type_definition.ex index aed14f5a9b..1a985002c7 100644 --- a/lib/absinthe/blueprint/schema/union_type_definition.ex +++ b/lib/absinthe/blueprint/schema/union_type_definition.ex @@ -80,7 +80,9 @@ defmodule Absinthe.Blueprint.Schema.UnionTypeDefinition do description: arg_def.description, type: Blueprint.TypeReference.to_type(arg_def.type, schema), default_value: arg_def.default_value, - deprecation: arg_def.deprecation + deprecation: arg_def.deprecation, + __reference__: arg_def.__reference__, + __private__: arg_def.__private__ } {arg_def.identifier, arg} diff --git a/lib/absinthe/type/argument.ex b/lib/absinthe/type/argument.ex index 2edafa5ff2..61b027ec1d 100644 --- a/lib/absinthe/type/argument.ex +++ b/lib/absinthe/type/argument.ex @@ -15,6 +15,8 @@ defmodule Absinthe.Type.Argument do * `:deprecation` - Deprecation information for an argument, usually set-up using `Absinthe.Schema.Notation.deprecate/1`. * `:description` - Description of an argument, useful for introspection. + + The `__private__` and `__reference__` fields are for internal use. """ @type t :: %__MODULE__{ name: binary, @@ -23,7 +25,8 @@ defmodule Absinthe.Type.Argument do deprecation: Type.Deprecation.t() | nil, description: binary | nil, definition: module, - __reference__: Type.Reference.t() + __reference__: Type.Reference.t(), + __private__: Keyword.t(), } defstruct identifier: nil, @@ -33,5 +36,6 @@ defmodule Absinthe.Type.Argument do deprecation: nil, default_value: nil, definition: nil, - __reference__: nil + __reference__: nil, + __private__: [] end From ec5a0e7d7ab667ecf3ee9949df5ae0b7e624c394 Mon Sep 17 00:00:00 2001 From: Daniel Reigada Date: Fri, 11 Aug 2023 10:18:54 +0100 Subject: [PATCH 14/36] Add __private__ field to Absinthe.Type.Enum.Value struct --- lib/absinthe/blueprint/schema/enum_type_definition.ex | 1 + lib/absinthe/type/enum/value.ex | 8 ++++++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/lib/absinthe/blueprint/schema/enum_type_definition.ex b/lib/absinthe/blueprint/schema/enum_type_definition.ex index 7afbb43248..aa80a952d0 100644 --- a/lib/absinthe/blueprint/schema/enum_type_definition.ex +++ b/lib/absinthe/blueprint/schema/enum_type_definition.ex @@ -50,6 +50,7 @@ defmodule Absinthe.Blueprint.Schema.EnumTypeDefinition do value: value_def.value, enum_identifier: type_def.identifier, __reference__: value_def.__reference__, + __private__: value_def.__private__, description: value_def.description, deprecation: value_def.deprecation } diff --git a/lib/absinthe/type/enum/value.ex b/lib/absinthe/type/enum/value.ex index 8a8ec4e275..669d04c0b3 100644 --- a/lib/absinthe/type/enum/value.ex +++ b/lib/absinthe/type/enum/value.ex @@ -21,6 +21,8 @@ defmodule Absinthe.Type.Enum.Value do * `:deprecation` - Deprecation information for a value, usually set-up using the `Absinthe.Schema.Notation.deprecate/1` convenience function. + + The `__private__` and `__reference__` fields are for internal use. """ @type t :: %{ name: binary, @@ -28,12 +30,14 @@ defmodule Absinthe.Type.Enum.Value do value: any, enum_identifier: atom, deprecation: Type.Deprecation.t() | nil, - __reference__: Type.Reference.t() + __reference__: Type.Reference.t(), + __private__: Keyword.t() } defstruct name: nil, description: nil, value: nil, deprecation: nil, enum_identifier: nil, - __reference__: nil + __reference__: nil, + __private__: [] end From 65d6dde17f94d061c49f23c5bade37f252f8490b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Fri, 11 Aug 2023 12:07:51 +0200 Subject: [PATCH 15/36] Store less information in the registry Instead of storing data in the registry keyed by self(), we put that in the process dictionary. Although the process dictionary is discouraged, they are equivalent here: they are both a form of side-effect although pdict is considerably less expensive. --- lib/absinthe/subscription.ex | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/lib/absinthe/subscription.ex b/lib/absinthe/subscription.ex index 7563e5f064..2540f043b3 100644 --- a/lib/absinthe/subscription.ex +++ b/lib/absinthe/subscription.ex @@ -151,8 +151,20 @@ defmodule Absinthe.Subscription do } } + pdict_add_field(doc_id, field_key) {:ok, _} = Registry.register(registry, field_key, doc_value) - {:ok, _} = Registry.register(registry, {self(), doc_id}, field_key) + end + + defp pdict_fields(doc_id) do + Process.get({__MODULE__, doc_id}, []) + end + + defp pdict_add_field(doc_id, field) do + Process.put({__MODULE__, doc_id}, [field | pdict_fields(doc_id)]) + end + + defp pdict_delete_fields(doc_id) do + Process.delete({__MODULE__, doc_id}) end @doc false @@ -160,11 +172,11 @@ defmodule Absinthe.Subscription do registry = pubsub |> registry_name self = self() - for {^self, field_key} <- Registry.lookup(registry, {self, doc_id}) do + for field_key <- pdict_fields(doc_id) do Registry.unregister_match(registry, field_key, {doc_id, :_}) end - Registry.unregister(registry, {self, doc_id}) + pdict_delete_fields(doc_id) :ok end From afdc6517b5d4eab1c17cb99ce0a69db948c87b32 Mon Sep 17 00:00:00 2001 From: Ben Wilson Date: Fri, 11 Aug 2023 09:22:43 -0400 Subject: [PATCH 16/36] remove unused variable --- lib/absinthe/subscription.ex | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/absinthe/subscription.ex b/lib/absinthe/subscription.ex index 2540f043b3..8d07db18f1 100644 --- a/lib/absinthe/subscription.ex +++ b/lib/absinthe/subscription.ex @@ -170,7 +170,6 @@ defmodule Absinthe.Subscription do @doc false def unsubscribe(pubsub, doc_id) do registry = pubsub |> registry_name - self = self() for field_key <- pdict_fields(doc_id) do Registry.unregister_match(registry, field_key, {doc_id, :_}) From 78c6fc1699bce5f9ec750b716481b424bcddd093 Mon Sep 17 00:00:00 2001 From: Alex Martsinovich Date: Mon, 14 Aug 2023 13:08:20 -0700 Subject: [PATCH 17/36] Register doc under doc_id key rather than field_key --- lib/absinthe/subscription.ex | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/lib/absinthe/subscription.ex b/lib/absinthe/subscription.ex index 8d07db18f1..df1579345c 100644 --- a/lib/absinthe/subscription.ex +++ b/lib/absinthe/subscription.ex @@ -143,16 +143,14 @@ defmodule Absinthe.Subscription do def subscribe(pubsub, field_key, doc_id, doc) do registry = pubsub |> registry_name - doc_value = { - doc_id, - %{ - initial_phases: PipelineSerializer.pack(doc.initial_phases), - source: doc.source - } + doc_value = %{ + initial_phases: PipelineSerializer.pack(doc.initial_phases), + source: doc.source } pdict_add_field(doc_id, field_key) - {:ok, _} = Registry.register(registry, field_key, doc_value) + {:ok, _} = Registry.register(registry, field_key, doc_id) + {:ok, _} = Registry.register(registry, doc_id, doc_value) end defp pdict_fields(doc_id) do @@ -172,9 +170,11 @@ defmodule Absinthe.Subscription do registry = pubsub |> registry_name for field_key <- pdict_fields(doc_id) do - Registry.unregister_match(registry, field_key, {doc_id, :_}) + Registry.unregister(registry, field_key) end + Registry.unregister(registry, doc_id) + pdict_delete_fields(doc_id) :ok end @@ -184,7 +184,17 @@ defmodule Absinthe.Subscription do pubsub |> registry_name |> Registry.lookup(key) - |> Map.new(fn {_, {doc_id, doc}} -> + |> Enum.map(fn {_, doc_id} -> doc_id end) + |> then(fn doc_ids -> + pubsub + |> registry_name + |> Registry.select( + # We compose a list of match specs that basically mean "lookup all keys + # in the doc_ids list" + for k <- doc_ids, do: {{:"$1", :_, :"$2"}, [{:==, :"$1", k}], [{{:"$1", :"$2"}}]} + ) + end) + |> Map.new(fn {doc_id, doc} -> doc = Map.update!(doc, :initial_phases, &PipelineSerializer.unpack/1) {doc_id, doc} From 2f4931f70d007eab1221500b0b7dbfa825ac3677 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Mon, 14 Aug 2023 23:59:55 +0200 Subject: [PATCH 18/36] Also pack contexts and variables in subscriptions --- .../subscription/pipeline_serializer.ex | 62 ++++++++++++++----- .../subscription/pipeline_serializer_test.exs | 46 +++++++++++--- 2 files changed, 82 insertions(+), 26 deletions(-) diff --git a/lib/absinthe/subscription/pipeline_serializer.ex b/lib/absinthe/subscription/pipeline_serializer.ex index e93848b227..a0ba835a81 100644 --- a/lib/absinthe/subscription/pipeline_serializer.ex +++ b/lib/absinthe/subscription/pipeline_serializer.ex @@ -6,6 +6,8 @@ defmodule Absinthe.Subscription.PipelineSerializer do backed registry stores them flat in the memory). """ + @packed_keys [:variables, :context] + alias Absinthe.{Phase, Pipeline} @type options_label :: {:options, non_neg_integer()} @@ -18,21 +20,21 @@ defmodule Absinthe.Subscription.PipelineSerializer do @spec pack(Pipeline.t()) :: packed_pipeline() def pack(pipeline) do - {packed_pipeline, options_reverse_map} = + {packed_pipeline, reverse_map} = pipeline |> List.flatten() |> Enum.map_reduce(%{}, &maybe_pack_phase/2) - options_map = Map.new(options_reverse_map, fn {options, label} -> {label, options} end) + options_map = Map.new(reverse_map, fn {options, label} -> {label, options} end) {:packed, packed_pipeline, options_map} end @spec unpack(Pipeline.t() | packed_pipeline()) :: Pipeline.t() - def unpack({:packed, pipeline, options_map}) do + def unpack({:packed, pipeline, pack_map}) do Enum.map(pipeline, fn - {phase, {:options, _n} = options_label} -> - {phase, Map.fetch!(options_map, options_label)} + {phase, [:pack | index]} -> + {phase, pack_map |> Map.fetch!(index) |> maybe_unpack_options(pack_map)} phase -> phase @@ -43,21 +45,49 @@ defmodule Absinthe.Subscription.PipelineSerializer do pipeline end - defp maybe_pack_phase({phase, options}, options_reverse_map) do - if Map.has_key?(options_reverse_map, options) do - options_label = options_reverse_map[options] + defp maybe_pack_phase({phase, options}, reverse_map) do + {options, reverse_map} = maybe_pack_options(options, reverse_map) + {packed, reverse_map} = pack_value(options, reverse_map) + {{phase, packed}, reverse_map} + end + + defp maybe_pack_phase(phase, reverse_map) do + {phase, reverse_map} + end + + defp maybe_pack_options(options, reverse_map) do + for key <- @packed_keys, reduce: {options, reverse_map} do + {options, reverse_map} -> + value = Keyword.get(options, key, %{}) + + if value == %{} do + {options, reverse_map} + else + {packed, reverse_map} = pack_value(value, reverse_map) + {Keyword.put(options, key, packed), reverse_map} + end + end + end - {{phase, options_label}, options_reverse_map} - else - new_index = map_size(options_reverse_map) - options_label = {:options, new_index} - options_reverse_map = Map.put(options_reverse_map, options, options_label) + defp pack_value(key, reverse_map) do + case reverse_map do + %{^key => index} -> + {[:pack | index], reverse_map} - {{phase, options_label}, options_reverse_map} + %{} -> + new_index = map_size(reverse_map) + reverse_map = Map.put(reverse_map, key, new_index) + {[:pack | new_index], reverse_map} end end - defp maybe_pack_phase(phase, options_reverse_map) do - {phase, options_reverse_map} + defp maybe_unpack_options(options, pack_map) do + for key <- @packed_keys, reduce: options do + options -> + case Keyword.get(options, key) do + [:pack | index] -> Keyword.put(options, key, Map.fetch!(pack_map, index)) + _ -> options + end + end end end diff --git a/test/absinthe/subscription/pipeline_serializer_test.exs b/test/absinthe/subscription/pipeline_serializer_test.exs index 70a519987b..693bfc08de 100644 --- a/test/absinthe/subscription/pipeline_serializer_test.exs +++ b/test/absinthe/subscription/pipeline_serializer_test.exs @@ -16,7 +16,7 @@ defmodule Absinthe.Subscription.PipelineSerializerTest do test "packs full-fledged pipeline successfully" do pipeline = Pipeline.for_document(Schema, some: :option) - assert {:packed, [_ | _], %{{:options, 0} => options}} = PipelineSerializer.pack(pipeline) + assert {:packed, [_ | _], %{0 => options}} = PipelineSerializer.pack(pipeline) assert options[:some] == :option end @@ -30,12 +30,35 @@ defmodule Absinthe.Subscription.PipelineSerializerTest do assert {:packed, [ - {Phase1, {:options, 0}}, + {Phase1, [:pack | 0]}, Phase2, - {Phase3, {:options, 1}}, - {Phase4, {:options, 0}} + {Phase3, [:pack | 1]}, + {Phase4, [:pack | 0]} ], - %{{:options, 0} => [option1: :value1], {:options, 1} => [option2: :value2]}} = + %{0 => [option1: :value1], 1 => [option2: :value2]}} = + PipelineSerializer.pack(pipeline) + end + + test "packs variables and contexts in options" do + pipeline = [ + {Phase1, [context: %{large: 123}]}, + Phase2, + {Phase3, [context: %{large: 123}, another: 456]}, + {Phase4, [context: %{large: 123}]} + ] + + assert {:packed, + [ + {Phase1, [:pack | 1]}, + Phase2, + {Phase3, [:pack | 2]}, + {Phase4, [:pack | 1]} + ], + %{ + 0 => %{large: 123}, + 1 => [context: [:pack | 0]], + 2 => [context: [:pack | 0], another: 456] + }} = PipelineSerializer.pack(pipeline) end end @@ -75,9 +98,12 @@ defmodule Absinthe.Subscription.PipelineSerializerTest do test "flattens nested pipeline in full pack/unpack cycle" do pipeline = [ - {Phase1, [option1: :value1]}, + {Phase1, [context: %{large: :foobar}, option1: :value1]}, Phase2, - [{Phase3, [option2: :value2]}, {Phase4, [option1: :value1]}] + [ + {Phase3, [variables: %{some: :thing}, option2: :value2]}, + {Phase4, [context: %{large: :foobar}, option1: :value1]} + ] ] unpacked = @@ -86,10 +112,10 @@ defmodule Absinthe.Subscription.PipelineSerializerTest do |> PipelineSerializer.unpack() assert unpacked == [ - {Phase1, [option1: :value1]}, + {Phase1, [context: %{large: :foobar}, option1: :value1]}, Phase2, - {Phase3, [option2: :value2]}, - {Phase4, [option1: :value1]} + {Phase3, [variables: %{some: :thing}, option2: :value2]}, + {Phase4, [context: %{large: :foobar}, option1: :value1]} ] end end From 275fa7da6ceef3b4610d7ee40bc28e1e90496dc3 Mon Sep 17 00:00:00 2001 From: Ben Wilson Date: Mon, 14 Aug 2023 18:28:29 -0400 Subject: [PATCH 19/36] Update lib/absinthe/subscription.ex MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: José Valim --- lib/absinthe/subscription.ex | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/absinthe/subscription.ex b/lib/absinthe/subscription.ex index df1579345c..64dae6ade5 100644 --- a/lib/absinthe/subscription.ex +++ b/lib/absinthe/subscription.ex @@ -184,14 +184,13 @@ defmodule Absinthe.Subscription do pubsub |> registry_name |> Registry.lookup(key) - |> Enum.map(fn {_, doc_id} -> doc_id end) |> then(fn doc_ids -> pubsub |> registry_name |> Registry.select( # We compose a list of match specs that basically mean "lookup all keys # in the doc_ids list" - for k <- doc_ids, do: {{:"$1", :_, :"$2"}, [{:==, :"$1", k}], [{{:"$1", :"$2"}}]} + for {_, doc_id} <- doc_ids, do: {{:"$1", :_, :"$2"}, [{:==, :"$1", doc_id}], [{{:"$1", :"$2"}}]} ) end) |> Map.new(fn {doc_id, doc} -> From 7b1c16a6e0c281414012062f589d1d02d45b19db Mon Sep 17 00:00:00 2001 From: Ben Wilson Date: Mon, 14 Aug 2023 22:40:46 -0400 Subject: [PATCH 20/36] formatting --- lib/absinthe/subscription.ex | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/absinthe/subscription.ex b/lib/absinthe/subscription.ex index 64dae6ade5..60058eb455 100644 --- a/lib/absinthe/subscription.ex +++ b/lib/absinthe/subscription.ex @@ -190,7 +190,8 @@ defmodule Absinthe.Subscription do |> Registry.select( # We compose a list of match specs that basically mean "lookup all keys # in the doc_ids list" - for {_, doc_id} <- doc_ids, do: {{:"$1", :_, :"$2"}, [{:==, :"$1", doc_id}], [{{:"$1", :"$2"}}]} + for {_, doc_id} <- doc_ids, + do: {{:"$1", :_, :"$2"}, [{:==, :"$1", doc_id}], [{{:"$1", :"$2"}}]} ) end) |> Map.new(fn {doc_id, doc} -> From 051173ef4d44a4a96d42c51848e70db6f2a04d13 Mon Sep 17 00:00:00 2001 From: Ben Wilson Date: Wed, 16 Aug 2023 07:14:08 -0400 Subject: [PATCH 21/36] fix formatting --- lib/absinthe/type/argument.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/absinthe/type/argument.ex b/lib/absinthe/type/argument.ex index 61b027ec1d..c91b09a845 100644 --- a/lib/absinthe/type/argument.ex +++ b/lib/absinthe/type/argument.ex @@ -26,7 +26,7 @@ defmodule Absinthe.Type.Argument do description: binary | nil, definition: module, __reference__: Type.Reference.t(), - __private__: Keyword.t(), + __private__: Keyword.t() } defstruct identifier: nil, From 173d6d15dd5ba7bcca387833bdb1bc32b8a462da Mon Sep 17 00:00:00 2001 From: Ben Wilson Date: Mon, 14 Aug 2023 23:04:13 -0400 Subject: [PATCH 22/36] support an mfa for the initial phases --- lib/absinthe/blueprint.ex | 2 +- lib/absinthe/subscription/pipeline_serializer.ex | 11 ++++++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/lib/absinthe/blueprint.ex b/lib/absinthe/blueprint.ex index ff66e5673d..496296a114 100644 --- a/lib/absinthe/blueprint.ex +++ b/lib/absinthe/blueprint.ex @@ -43,7 +43,7 @@ defmodule Absinthe.Blueprint do source: nil | String.t() | Absinthe.Language.Source.t(), execution: Blueprint.Execution.t(), result: result_t, - initial_phases: [Absinthe.Phase.t()] + initial_phases: [Absinthe.Phase.t()] | {module(), atom, list()} } @type result_t :: %{ diff --git a/lib/absinthe/subscription/pipeline_serializer.ex b/lib/absinthe/subscription/pipeline_serializer.ex index a0ba835a81..5f5ad6ad91 100644 --- a/lib/absinthe/subscription/pipeline_serializer.ex +++ b/lib/absinthe/subscription/pipeline_serializer.ex @@ -18,7 +18,12 @@ defmodule Absinthe.Subscription.PipelineSerializer do @type packed_pipeline :: {:packed, [packed_phase_config()], options_map()} - @spec pack(Pipeline.t()) :: packed_pipeline() + @spec pack(Pipeline.t() | {module(), atom, list()}) :: packed_pipeline() + def pack({module, function, args}) + when is_atom(module) and is_atom(function) and is_list(args) do + {module, function, args} + end + def pack(pipeline) do {packed_pipeline, reverse_map} = pipeline @@ -41,6 +46,10 @@ defmodule Absinthe.Subscription.PipelineSerializer do end) end + def unpack({module, function, args}) do + apply(module, function, args) + end + def unpack([_ | _] = pipeline) do pipeline end From 5ff8588b3b62574582033435cc4627e10f399939 Mon Sep 17 00:00:00 2001 From: Ben Wilson Date: Wed, 16 Aug 2023 08:57:32 -0400 Subject: [PATCH 23/36] store the document only once when several field keys are supplied --- .../phase/subscription/subscribe_self.ex | 3 +-- lib/absinthe/subscription.ex | 16 +++++++++++----- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/lib/absinthe/phase/subscription/subscribe_self.ex b/lib/absinthe/phase/subscription/subscribe_self.ex index ffb86860a6..3a4a97ec80 100644 --- a/lib/absinthe/phase/subscription/subscribe_self.ex +++ b/lib/absinthe/phase/subscription/subscribe_self.ex @@ -25,8 +25,7 @@ defmodule Absinthe.Phase.Subscription.SubscribeSelf do field_keys = get_field_keys(field, config) subscription_id = get_subscription_id(config, blueprint, options) - for field_key <- field_keys, - do: Absinthe.Subscription.subscribe(pubsub, field_key, subscription_id, blueprint) + Absinthe.Subscription.subscribe(pubsub, field_keys, subscription_id, blueprint) {:replace, blueprint, [ diff --git a/lib/absinthe/subscription.ex b/lib/absinthe/subscription.ex index 60058eb455..757d7c47be 100644 --- a/lib/absinthe/subscription.ex +++ b/lib/absinthe/subscription.ex @@ -140,7 +140,9 @@ defmodule Absinthe.Subscription do defp fetch_fields(_, _), do: [] @doc false - def subscribe(pubsub, field_key, doc_id, doc) do + def subscribe(pubsub, field_keys, doc_id, doc) do + field_keys = List.wrap(field_keys) + registry = pubsub |> registry_name doc_value = %{ @@ -148,8 +150,12 @@ defmodule Absinthe.Subscription do source: doc.source } - pdict_add_field(doc_id, field_key) - {:ok, _} = Registry.register(registry, field_key, doc_id) + pdict_add_fields(doc_id, field_keys) + + for field_key <- field_keys do + {:ok, _} = Registry.register(registry, field_key, doc_id) + end + {:ok, _} = Registry.register(registry, doc_id, doc_value) end @@ -157,8 +163,8 @@ defmodule Absinthe.Subscription do Process.get({__MODULE__, doc_id}, []) end - defp pdict_add_field(doc_id, field) do - Process.put({__MODULE__, doc_id}, [field | pdict_fields(doc_id)]) + defp pdict_add_fields(doc_id, field_keys) do + Process.put({__MODULE__, doc_id}, field_keys ++ pdict_fields(doc_id)) end defp pdict_delete_fields(doc_id) do From 28f26f62ddf6d90414a09ae144fcb30636719e45 Mon Sep 17 00:00:00 2001 From: Matt Baker Date: Tue, 5 Sep 2023 14:46:46 -0700 Subject: [PATCH 24/36] Add test demonstrating odd behavior --- .../absinthe/phase/execution/non_null_test.exs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/test/absinthe/phase/execution/non_null_test.exs b/test/absinthe/phase/execution/non_null_test.exs index f65d401b03..2369777cd4 100644 --- a/test/absinthe/phase/execution/non_null_test.exs +++ b/test/absinthe/phase/execution/non_null_test.exs @@ -96,6 +96,13 @@ defmodule Absinthe.Phase.Document.Execution.NonNullTest do resolve &things_resolver/3 end + field :non_null_list_of_non_null_list_of_nullable, + non_null(list_of(non_null(list_of(:string)))) do + resolve fn _, _ -> + {:ok, [["ok", nil]]} + end + end + @desc """ A field declared to be non null. @@ -217,6 +224,17 @@ defmodule Absinthe.Phase.Document.Execution.NonNullTest do end describe "lists" do + test "a field that is a non-null list of a non-null list of a nullable value will return a null value" do + doc = """ + { + nonNullListOfNonNullListOfNullable + } + """ + + assert {:ok, %{data: %{"nonNullListOfNonNullListOfNullable" => [["ok", nil]]}}} == + Absinthe.run(doc, Schema) + end + test "list of nullable things returns an error when child has a null violation" do doc = """ { From 3e5fafc747386249fba616bc8f94da5c71ffd20b Mon Sep 17 00:00:00 2001 From: Matt Baker Date: Fri, 8 Sep 2023 18:37:40 -0700 Subject: [PATCH 25/36] more tests --- .../phase/execution/non_null_test.exs | 52 ++++++++++++++++++- 1 file changed, 51 insertions(+), 1 deletion(-) diff --git a/test/absinthe/phase/execution/non_null_test.exs b/test/absinthe/phase/execution/non_null_test.exs index 2369777cd4..c14d401007 100644 --- a/test/absinthe/phase/execution/non_null_test.exs +++ b/test/absinthe/phase/execution/non_null_test.exs @@ -103,6 +103,22 @@ defmodule Absinthe.Phase.Document.Execution.NonNullTest do end end + field :deeply_nested_non_nullable_list_of_nullable, + non_null(list_of(non_null(list_of(non_null(list_of(non_null(list_of(:string)))))))) do + resolve fn _, _ -> + {:ok, [[[["ok", nil]]]]} + end + end + + field :deeply_nested_non_nullable_list_of_non_nullable, + non_null( + list_of(non_null(list_of(non_null(list_of(non_null(list_of(non_null(:string)))))))) + ) do + resolve fn _, _ -> + {:ok, [[[["1", nil, "3"], ["4", nil, "6"]]]]} + end + end + @desc """ A field declared to be non null. @@ -224,7 +240,7 @@ defmodule Absinthe.Phase.Document.Execution.NonNullTest do end describe "lists" do - test "a field that is a non-null list of a non-null list of a nullable value will return a null value" do + test "non-null list of non-null list of nullable value returns null value" do doc = """ { nonNullListOfNonNullListOfNullable @@ -235,6 +251,40 @@ defmodule Absinthe.Phase.Document.Execution.NonNullTest do Absinthe.run(doc, Schema) end + test "deeply nested nullable value inside non-nullable lists can be null" do + doc = """ + { + deeplyNestedNonNullableListOfNullable + } + """ + + assert {:ok, %{data: %{"deeplyNestedNonNullableListOfNullable" => [[[["ok", nil]]]]}}} == + Absinthe.run(doc, Schema) + end + + test "deeply nested non-nullable value inside non-nullable lists cannot be null" do + doc = """ + { + deeplyNestedNonNullableListOfNonNullable + } + """ + + errors = [ + %{ + locations: [%{column: 3, line: 2}], + message: "Cannot return null for non-nullable field", + path: ["deeplyNestedNonNullableListOfNonNullable", 0, 0, 0, 1] + }, + %{ + locations: [%{column: 3, line: 2}], + message: "Cannot return null for non-nullable field", + path: ["deeplyNestedNonNullableListOfNonNullable", 0, 0, 1, 1] + } + ] + + assert {:ok, %{data: nil, errors: errors}} == Absinthe.run(doc, Schema) + end + test "list of nullable things returns an error when child has a null violation" do doc = """ { From 006b3135e3be8c9fff138b431d80217bf6612947 Mon Sep 17 00:00:00 2001 From: Matt Baker Date: Fri, 8 Sep 2023 18:38:11 -0700 Subject: [PATCH 26/36] traverse deeply nested lists when searching for nullable violations --- .../phase/document/execution/resolution.ex | 64 ++++++++++--------- 1 file changed, 35 insertions(+), 29 deletions(-) diff --git a/lib/absinthe/phase/document/execution/resolution.ex b/lib/absinthe/phase/document/execution/resolution.ex index f831a5d613..a16a5c6f62 100644 --- a/lib/absinthe/phase/document/execution/resolution.ex +++ b/lib/absinthe/phase/document/execution/resolution.ex @@ -285,30 +285,28 @@ defmodule Absinthe.Phase.Document.Execution.Resolution do |> propagate_null_trimming end - defp maybe_add_non_null_error([], nil, %Type.NonNull{}) do + defp maybe_add_non_null_error(errors, value, type, path \\ []) + + defp maybe_add_non_null_error([], nil, %Type.NonNull{}, []) do ["Cannot return null for non-nullable field"] end - # Unwrap the non null so we can check again for the possible case of a non null - # inside of a list. We want that clause to handle both - # - `non_null(list_of(non_null(:thing)))` - # - `list_of(non_null(:thing))` - # Thus the single layer of unwrapping here. - defp maybe_add_non_null_error([], values, %Type.NonNull{of_type: type}) do - maybe_add_non_null_error([], values, type) + defp maybe_add_non_null_error([], nil, %Type.NonNull{}, path) do + [%{message: "Cannot return null for non-nullable field", path: path}] + end + + defp maybe_add_non_null_error([], value, %Type.NonNull{of_type: %Type.List{} = type}, path) do + maybe_add_non_null_error([], value, type, path) end - defp maybe_add_non_null_error([], values, %Type.List{of_type: %Type.NonNull{}}) - when is_list(values) do - values - |> Enum.with_index() - |> Enum.filter(&is_nil(elem(&1, 0))) - |> Enum.map(fn {_value, index} -> - %{message: "Cannot return null for non-nullable field", path: [index]} + defp maybe_add_non_null_error([], [_ | _] = values, %Type.List{of_type: type}, path) do + Enum.with_index(values) + |> Enum.flat_map(fn {value, index} -> + maybe_add_non_null_error([], value, type, path ++ [index]) end) end - defp maybe_add_non_null_error(errors, _, _) do + defp maybe_add_non_null_error(errors, _, _, _path) do errors end @@ -360,28 +358,36 @@ defmodule Absinthe.Phase.Document.Execution.Resolution do false end + defp non_null_list_violation?(%{values: values}) do + Enum.find(values, &non_null_list_violation?/1) + end + # FIXME: Not super happy with this lookup process. # Also it would be nice if we could use the same function as above. - defp non_null_list_violation?(%{ - value: nil, - emitter: %{schema_node: %{type: %Type.List{of_type: %Type.NonNull{}}}} - }) do - true + defp non_null_list_violation?(%{value: nil, emitter: %{schema_node: %{type: type}}}) do + !null_allowed_in_list?(type) end - defp non_null_list_violation?(%{ - value: nil, - emitter: %{ - schema_node: %{type: %Type.NonNull{of_type: %Type.List{of_type: %Type.NonNull{}}}} - } - }) do - true + defp non_null_list_violation?(_) do + false end - defp non_null_list_violation?(_) do + defp null_allowed_in_list?(%Type.List{of_type: wrapped_type}) do + null_allowed_in_list?(wrapped_type) + end + + defp null_allowed_in_list?(%Type.NonNull{of_type: %Type.List{of_type: wrapped_type}}) do + null_allowed_in_list?(wrapped_type) + end + + defp null_allowed_in_list?(%Type.NonNull{of_type: _wrapped_type}) do false end + defp null_allowed_in_list?(_type) do + true + end + defp add_errors(result, errors, fun) do Enum.reduce(errors, result, fun) end From a18c053911874d3ce01ad771750ae48f2c96e7a9 Mon Sep 17 00:00:00 2001 From: Christian Wesselhoeft Date: Fri, 27 Oct 2023 14:24:59 -0600 Subject: [PATCH 27/36] Update incorrect GraphQL website URLs --- README.md | 2 +- guides/introduction/overview.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index e1c7b039bb..eb1a64453f 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ [![License](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT) [![Last Updated](https://img.shields.io/github/last-commit/absinthe-graphql/absinthe.svg)](https://github.com/absinthe-graphql/absinthe/commits/master) -[GraphQL](https://github.com/graphql-elixir/graphql) implementation for Elixir. +[GraphQL](https://graphql.org) implementation for Elixir. Goals: diff --git a/guides/introduction/overview.md b/guides/introduction/overview.md index b371012850..cc163c37f2 100644 --- a/guides/introduction/overview.md +++ b/guides/introduction/overview.md @@ -1,6 +1,6 @@ # Overview -Absinthe is the GraphQL toolkit for Elixir, an implementation of the [GraphQL specification](https://github.com/graphql-elixir/graphql) built to suit the language's capabilities and idiomatic style. +Absinthe is the GraphQL toolkit for Elixir, an implementation of the [GraphQL specification](https://spec.graphql.org) built to suit the language's capabilities and idiomatic style. The Absinthe project consists of several complementary packages. You can find the full listing on the [absinthe-graphql](https://github.com/absinthe-graphql) GitHub organization page. From c92ddff543f07695356e926a666a4e3a8906387f Mon Sep 17 00:00:00 2001 From: Kian-Meng Ang Date: Wed, 1 Nov 2023 12:29:44 +0800 Subject: [PATCH 28/36] Fix typos Found via `codespell -S deps,cover -L symboll,boolen,whoknows,technics` --- lib/absinthe/schema/notation/sdl_render.ex | 10 +++++----- lib/absinthe/subscription.ex | 2 +- .../integration/execution/token_limit_enforcement.exs | 2 +- .../phase/document/arguments/coerce_lists_test.exs | 2 +- test/absinthe/resolution/middleware_test.exs | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/lib/absinthe/schema/notation/sdl_render.ex b/lib/absinthe/schema/notation/sdl_render.ex index baac5a2429..19a0bbcc82 100644 --- a/lib/absinthe/schema/notation/sdl_render.ex +++ b/lib/absinthe/schema/notation/sdl_render.ex @@ -351,11 +351,11 @@ defmodule Absinthe.Schema.Notation.SDL.Render do # Render Helpers - defp render_list(items, type_definitions, seperator \\ line()) + defp render_list(items, type_definitions, separator \\ line()) # Workaround for `values` macro which temporarily defines # values as raw atoms to support dynamic schemas - defp render_list([first | _] = items, type_definitions, seperator) when is_atom(first) do + defp render_list([first | _] = items, type_definitions, separator) when is_atom(first) do items |> Enum.map( &%Blueprint.Schema.EnumValueDefinition{ @@ -363,10 +363,10 @@ defmodule Absinthe.Schema.Notation.SDL.Render do name: String.upcase(to_string(&1)) } ) - |> render_list(type_definitions, seperator) + |> render_list(type_definitions, separator) end - defp render_list(items, type_definitions, seperator) do + defp render_list(items, type_definitions, separator) do items = Enum.reject(items, &(&1.module in @skip_modules)) splitter = @@ -374,7 +374,7 @@ defmodule Absinthe.Schema.Notation.SDL.Render do |> Enum.any?(&(&1.description not in ["", nil])) |> case do true -> [nest(line(), :reset), line()] - false -> [seperator] + false -> [separator] end items diff --git a/lib/absinthe/subscription.ex b/lib/absinthe/subscription.ex index 757d7c47be..6eed461311 100644 --- a/lib/absinthe/subscription.ex +++ b/lib/absinthe/subscription.ex @@ -44,7 +44,7 @@ defmodule Absinthe.Subscription do @doc """ Build a child specification for subscriptions. - In order to use supscriptions in your application, you must add + In order to use subscriptions in your application, you must add `Absinthe.Subscription` to your supervision tree after your endpoint. See `guides/subscriptions.md` for more information on how to get up and diff --git a/test/absinthe/integration/execution/token_limit_enforcement.exs b/test/absinthe/integration/execution/token_limit_enforcement.exs index e3d8e9e02b..6f5f6072c1 100644 --- a/test/absinthe/integration/execution/token_limit_enforcement.exs +++ b/test/absinthe/integration/execution/token_limit_enforcement.exs @@ -30,7 +30,7 @@ defmodule Elixir.Absinthe.Integration.Execution.TokenLimitEnforcement do """ # token count 33 = 8 braces + 2 parens + 2 brackets + 2 string values + 1 null + 1 float + 1 bool + - # 3 colons + 1 ... + 1 on + 0 ignroed comment + 1@ + 1 directive + 9 names + # 3 colons + 1 ... + 1 on + 0 ignored comment + 1@ + 1 directive + 9 names assert {:ok, %{errors: [%{message: "Token limit exceeded"}]}} == Absinthe.run(query, Absinthe.Fixtures.Things.MacroSchema, token_limit: 32) diff --git a/test/absinthe/phase/document/arguments/coerce_lists_test.exs b/test/absinthe/phase/document/arguments/coerce_lists_test.exs index f7c2de8438..d206db1405 100644 --- a/test/absinthe/phase/document/arguments/coerce_lists_test.exs +++ b/test/absinthe/phase/document/arguments/coerce_lists_test.exs @@ -112,7 +112,7 @@ defmodule Absinthe.Phase.Document.Arguments.CoerceListsTest do end end - describe "when using a List of a coercable type input argument" do + describe "when using a List of a coercible type input argument" do test "coerces the type from a single element to List" do doc = """ query List { diff --git a/test/absinthe/resolution/middleware_test.exs b/test/absinthe/resolution/middleware_test.exs index 1dcb56907f..1d06ea09f9 100644 --- a/test/absinthe/resolution/middleware_test.exs +++ b/test/absinthe/resolution/middleware_test.exs @@ -150,7 +150,7 @@ defmodule Absinthe.MiddlewareTest do assert %{"public" => %{"email" => "secret", "name" => "bob"}} == data end - test "secret object cant be accessed without a current user" do + test "secret object can't be accessed without a current user" do doc = """ {returnsPrivateObject { key }} """ From de79dda0360280970179d527a97b50c71ca6ac34 Mon Sep 17 00:00:00 2001 From: Jerel Miller Date: Fri, 10 Nov 2023 23:57:05 -0700 Subject: [PATCH 29/36] Update Apollo Client documentation to use Apollo Client v3 imports --- guides/client/apollo.md | 41 ++++++++++++++++++++++------------------- 1 file changed, 22 insertions(+), 19 deletions(-) diff --git a/guides/client/apollo.md b/guides/client/apollo.md index dd3c2be807..6591439500 100644 --- a/guides/client/apollo.md +++ b/guides/client/apollo.md @@ -7,9 +7,7 @@ An Apollo client manages its connection to the GraphQL server using [links](http Using Apollo with an HTTP link does not require any Absinthe-specific configuration. You can create an HTTP link pointed at your Absinthe server as follows: ```javascript -import ApolloClient from "apollo-client"; -import { createHttpLink } from "apollo-link-http"; -import { InMemoryCache } from "apollo-cache-inmemory"; +import { ApolloClient, InMemoryCache, createHttpLink } from "@apollo/client"; // Create an HTTP link to the Absinthe server. const link = createHttpLink({ @@ -31,10 +29,8 @@ const client = new ApolloClient({ You may find that you need to modify the HTTP request that Apollo makes -- for example, if you wish to send the value of a particular cookie in the `Authorization` header. The `setContext` helper allows you to do this, and also demonstrates how links in Apollo can be chained. ```javascript -import ApolloClient from "apollo-client"; -import { createHttpLink } from "apollo-link-http"; -import { setContext } from "apollo-link-context"; -import { InMemoryCache } from "apollo-cache-inmemory"; +import { ApolloClient, InMemoryCache, createHttpLink } from "@apollo/client"; +import { setContext } from "@apollo/client/link/context"; import Cookies from "js-cookie"; // Create an HTTP link to the Absinthe server. @@ -77,11 +73,10 @@ const client = new ApolloClient({ An HTTP link is suitable for many basic use cases, but if you require two-way communication between the server and the client, you will need to use a websocket link. The most common use case is a client that needs to use GraphQL subscriptions to receive updates from the server when particular events occur. To implement a websocket link, we will need to use the [`@absinthe/socket`](https://www.npmjs.com/package/@absinthe/socket) and [`@absinthe/socket-apollo-link`](https://www.npmjs.com/package/@absinthe/socket-apollo-link) packages. ```javascript -import ApolloClient from "apollo-client"; +import { ApolloClient, InMemoryCache } from "@apollo/client"; import * as AbsintheSocket from "@absinthe/socket"; import { createAbsintheSocketLink } from "@absinthe/socket-apollo-link"; import { Socket as PhoenixSocket } from "phoenix"; -import { InMemoryCache } from "apollo-cache-inmemory"; import Cookies from "js-cookie"; // Create a standard Phoenix websocket connection. If you need @@ -122,11 +117,10 @@ You may find that you periodically need to reconnect the websocket with differen Note that this solution (reconnecting with `phoenixSocket.conn.close();`) is somewhat unstable because it relies upon an implementation detail of the Phoenix socket. Ideally, a future version of the Phoenix package might add a public API method to reconnect the websocket with new parameters. ```javascript -import ApolloClient from "apollo-client"; +import { ApolloClient, InMemoryCache } from "@apollo/client"; import * as AbsintheSocket from "@absinthe/socket"; import { createAbsintheSocketLink } from "@absinthe/socket-apollo-link"; import { Socket as PhoenixSocket } from "phoenix"; -import { InMemoryCache } from "apollo-cache-inmemory"; import Cookies from "js-cookie"; // Create a standard Phoenix websocket connection. If you need @@ -171,18 +165,20 @@ phoenixSocket.conn.close(); ## Using both HTTP and websocket links -A common configuration for Apollo client applications is to use both HTTP and websocket links -- HTTP for queries and mutations, and a websocket for subscriptions. We can implement this in our client using [directional composition with Apollo's `split` helper](https://www.apollographql.com/docs/link/composition#directional). +A common configuration for Apollo client applications is to use both HTTP and websocket links -- HTTP for queries and mutations, and a websocket for subscriptions. We can implement this in our client using [directional composition with Apollo's `split` helper](https://www.apollographql.com/docs/react/api/link/introduction/#directional-composition). ```javascript -import ApolloClient from "apollo-client"; -import { createHttpLink } from "apollo-link-http"; -import { setContext } from "apollo-link-context"; +import { + ApolloClient, + InMemoryCache, + createHttpLink, + split +} from "@apollo/client"; +import { getMainDefinition } from "@apollo/client/utilities"; +import { setContext } from "@apollo/client/link/context"; import * as AbsintheSocket from "@absinthe/socket"; import { createAbsintheSocketLink } from "@absinthe/socket-apollo-link"; import { Socket as PhoenixSocket } from "phoenix"; -import { hasSubscription } from "@jumpn/utils-graphql"; -import { split } from "apollo-link"; -import { InMemoryCache } from "apollo-cache-inmemory"; import Cookies from "js-cookie"; // Create an HTTP link to the Absinthe server. @@ -230,7 +226,14 @@ const websocketLink = createAbsintheSocketLink(absintheSocket); // If the query contains a subscription, send it through the // websocket link. Otherwise, send it through the HTTP link. const link = split( - operation => hasSubscription(operation.query), + (operation) => { + const definition = getMainDefinition(query); + + return ( + definition.kind === "OperationDefinition" && + definition.operation === "subscription" + ); + }, websocketLink, authedHttpLink ); From c934a6fce0178efbcf8129d5c1b84ead652b171a Mon Sep 17 00:00:00 2001 From: Jonas Hartmann Date: Thu, 16 Nov 2023 15:21:10 +0100 Subject: [PATCH 30/36] relax nimble parsec version --- mix.exs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mix.exs b/mix.exs index c37d6ce184..4a3e32ad17 100644 --- a/mix.exs +++ b/mix.exs @@ -73,7 +73,7 @@ defmodule Absinthe.Mixfile do defp deps do [ - {:nimble_parsec, "~> 1.2.2 or ~> 1.3.0"}, + {:nimble_parsec, "~> 1.2.2 or ~> 1.3"}, {:telemetry, "~> 1.0 or ~> 0.4"}, {:dataloader, "~> 1.0.0 or ~> 2.0", optional: true}, {:decimal, "~> 1.0 or ~> 2.0", optional: true}, From 25b5049de5f1e5591acd60ba1b00604f8680a754 Mon Sep 17 00:00:00 2001 From: Jonas Hartmann Date: Thu, 16 Nov 2023 15:22:16 +0100 Subject: [PATCH 31/36] update version --- mix.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mix.lock b/mix.lock index 4b9eb9f757..8187e102f1 100644 --- a/mix.lock +++ b/mix.lock @@ -13,7 +13,7 @@ "makeup_erlang": {:hex, :makeup_erlang, "0.1.2", "ad87296a092a46e03b7e9b0be7631ddcf64c790fa68a9ef5323b6cbb36affc72", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "f3f5a1ca93ce6e092d92b6d9c049bcda58a3b617a8d888f8e7231c85630e8108"}, "makeup_graphql": {:hex, :makeup_graphql, "0.1.2", "81e2939aab6d2b81d39ee5d9e13fae02599e9ca6e1152e0eeed737a98a5f96aa", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.1", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "3390ab04ba388d52a94bbe64ef62aa4d7923ceaffac43ec948f58f631440e8fb"}, "mix_test_watch": {:hex, :mix_test_watch, "1.1.0", "330bb91c8ed271fe408c42d07e0773340a7938d8a0d281d57a14243eae9dc8c3", [:mix], [{:file_system, "~> 0.2.1 or ~> 0.3", [hex: :file_system, repo: "hexpm", optional: false]}], "hexpm", "52b6b1c476cbb70fd899ca5394506482f12e5f6b0d6acff9df95c7f1e0812ec3"}, - "nimble_parsec": {:hex, :nimble_parsec, "1.3.1", "2c54013ecf170e249e9291ed0a62e5832f70a476c61da16f6aac6dca0189f2af", [:mix], [], "hexpm", "2682e3c0b2eb58d90c6375fc0cc30bc7be06f365bf72608804fb9cffa5e1b167"}, + "nimble_parsec": {:hex, :nimble_parsec, "1.4.0", "51f9b613ea62cfa97b25ccc2c1b4216e81df970acd8e16e8d1bdc58fef21370d", [:mix], [], "hexpm", "9c565862810fb383e9838c1dd2d7d2c437b3d13b267414ba6af33e50d2d1cf28"}, "opentelemetry_api": {:hex, :opentelemetry_api, "1.2.0", "454a35655b4c1924405ef1f3587f2c6f141bf73366b2c5e8a38dcc619b53eaa0", [:mix, :rebar3], [], "hexpm", "9e677c68243de0f70538798072e66e1fb1d4a2ca8888a6eb493c0a41e5480c35"}, "opentelemetry_process_propagator": {:hex, :opentelemetry_process_propagator, "0.2.1", "20ac37648faf7175cade16fda8d58e6f1ff1b7f2a50a8ef9d70a032c41aba315", [:mix, :rebar3], [{:opentelemetry_api, "~> 1.0", [hex: :opentelemetry_api, repo: "hexpm", optional: false]}], "hexpm", "f317237e39636d4f6140afa5d419e85ed3dc9e9a57072e7cd442df42af7b8aac"}, "statistex": {:hex, :statistex, "1.0.0", "f3dc93f3c0c6c92e5f291704cf62b99b553253d7969e9a5fa713e5481cd858a5", [:mix], [], "hexpm", "ff9d8bee7035028ab4742ff52fc80a2aa35cece833cf5319009b52f1b5a86c27"}, From 639b1a2e366535edf667fafc4f5cc84e4362a25c Mon Sep 17 00:00:00 2001 From: Jonas Hartmann Date: Thu, 16 Nov 2023 15:28:24 +0100 Subject: [PATCH 32/36] update dialyxir --- mix.exs | 2 +- mix.lock | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/mix.exs b/mix.exs index c37d6ce184..c04b05dbf3 100644 --- a/mix.exs +++ b/mix.exs @@ -80,7 +80,7 @@ defmodule Absinthe.Mixfile do {:opentelemetry_process_propagator, "~> 0.2.1", optional: true}, {:ex_doc, "~> 0.22", only: :dev}, {:benchee, ">= 1.0.0", only: :dev}, - {:dialyxir, "~> 1.1.0", only: [:dev, :test], runtime: false}, + {:dialyxir, "~> 1.1", only: [:dev, :test], runtime: false}, {:mix_test_watch, "~> 1.0", only: :dev, runtime: false}, {:makeup_graphql, "~> 0.1.0", only: :dev} ] diff --git a/mix.lock b/mix.lock index 4b9eb9f757..b6357b994b 100644 --- a/mix.lock +++ b/mix.lock @@ -3,7 +3,7 @@ "dataloader": {:hex, :dataloader, "1.0.8", "114294362db98a613f231589246aa5b0ce847412e8e75c4c94f31f204d272cbf", [:mix], [{:ecto, ">= 3.4.3 and < 4.0.0", [hex: :ecto, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "eaf3c2aa2bc9dbd2f1e960561d616b7f593396c4754185b75904f6d66c82a667"}, "decimal": {:hex, :decimal, "2.0.0", "a78296e617b0f5dd4c6caf57c714431347912ffb1d0842e998e9792b5642d697", [:mix], [], "hexpm", "34666e9c55dea81013e77d9d87370fe6cb6291d1ef32f46a1600230b1d44f577"}, "deep_merge": {:hex, :deep_merge, "1.0.0", "b4aa1a0d1acac393bdf38b2291af38cb1d4a52806cf7a4906f718e1feb5ee961", [:mix], [], "hexpm", "ce708e5f094b9cd4e8f2be4f00d2f4250c4095be93f8cd6d018c753894885430"}, - "dialyxir": {:hex, :dialyxir, "1.1.0", "c5aab0d6e71e5522e77beff7ba9e08f8e02bad90dfbeffae60eaf0cb47e29488", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "07ea8e49c45f15264ebe6d5b93799d4dd56a44036cf42d0ad9c960bc266c0b9a"}, + "dialyxir": {:hex, :dialyxir, "1.4.2", "764a6e8e7a354f0ba95d58418178d486065ead1f69ad89782817c296d0d746a5", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "516603d8067b2fd585319e4b13d3674ad4f314a5902ba8130cd97dc902ce6bbd"}, "earmark_parser": {:hex, :earmark_parser, "1.4.32", "fa739a0ecfa34493de19426681b23f6814573faee95dfd4b4aafe15a7b5b32c6", [:mix], [], "hexpm", "b8b0dd77d60373e77a3d7e8afa598f325e49e8663a51bcc2b88ef41838cca755"}, "erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"}, "ex_doc": {:hex, :ex_doc, "0.29.4", "6257ecbb20c7396b1fe5accd55b7b0d23f44b6aa18017b415cb4c2b91d997729", [:mix], [{:earmark_parser, "~> 1.4.31", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "2c6699a737ae46cb61e4ed012af931b57b699643b24dabe2400a8168414bc4f5"}, From 20b385c603b94e9520a93f18da075efdb72f05d1 Mon Sep 17 00:00:00 2001 From: Ben Wilson Date: Fri, 17 Nov 2023 09:38:18 -0500 Subject: [PATCH 33/36] update dep locks --- mix.lock | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/mix.lock b/mix.lock index 30e2b0f60f..472f9ca11d 100644 --- a/mix.lock +++ b/mix.lock @@ -1,21 +1,22 @@ %{ - "benchee": {:hex, :benchee, "1.1.0", "f3a43817209a92a1fade36ef36b86e1052627fd8934a8b937ac9ab3a76c43062", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}], "hexpm", "7da57d545003165a012b587077f6ba90b89210fd88074ce3c60ce239eb5e6d93"}, - "dataloader": {:hex, :dataloader, "1.0.8", "114294362db98a613f231589246aa5b0ce847412e8e75c4c94f31f204d272cbf", [:mix], [{:ecto, ">= 3.4.3 and < 4.0.0", [hex: :ecto, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "eaf3c2aa2bc9dbd2f1e960561d616b7f593396c4754185b75904f6d66c82a667"}, - "decimal": {:hex, :decimal, "2.0.0", "a78296e617b0f5dd4c6caf57c714431347912ffb1d0842e998e9792b5642d697", [:mix], [], "hexpm", "34666e9c55dea81013e77d9d87370fe6cb6291d1ef32f46a1600230b1d44f577"}, + "benchee": {:hex, :benchee, "1.2.0", "afd2f0caec06ce3a70d9c91c514c0b58114636db9d83c2dc6bfd416656618353", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "ee729e53217898b8fd30aaad3cce61973dab61574ae6f48229fe7ff42d5e4457"}, + "dataloader": {:hex, :dataloader, "2.0.0", "49b42d60b9bb06d761a71d7b034c4b34787957e713d4fae15387a25fcd639112", [:mix], [{:ecto, ">= 3.4.3 and < 4.0.0", [hex: :ecto, repo: "hexpm", optional: true]}, {:opentelemetry_process_propagator, "~> 0.2.1", [hex: :opentelemetry_process_propagator, repo: "hexpm", optional: true]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "09d61781b76ce216e395cdbc883ff00d00f46a503e215c22722dba82507dfef0"}, + "decimal": {:hex, :decimal, "2.1.1", "5611dca5d4b2c3dd497dec8f68751f1f1a54755e8ed2a966c2633cf885973ad6", [:mix], [], "hexpm", "53cfe5f497ed0e7771ae1a475575603d77425099ba5faef9394932b35020ffcc"}, "deep_merge": {:hex, :deep_merge, "1.0.0", "b4aa1a0d1acac393bdf38b2291af38cb1d4a52806cf7a4906f718e1feb5ee961", [:mix], [], "hexpm", "ce708e5f094b9cd4e8f2be4f00d2f4250c4095be93f8cd6d018c753894885430"}, "dialyxir": {:hex, :dialyxir, "1.4.2", "764a6e8e7a354f0ba95d58418178d486065ead1f69ad89782817c296d0d746a5", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "516603d8067b2fd585319e4b13d3674ad4f314a5902ba8130cd97dc902ce6bbd"}, - "earmark_parser": {:hex, :earmark_parser, "1.4.32", "fa739a0ecfa34493de19426681b23f6814573faee95dfd4b4aafe15a7b5b32c6", [:mix], [], "hexpm", "b8b0dd77d60373e77a3d7e8afa598f325e49e8663a51bcc2b88ef41838cca755"}, + "earmark_parser": {:hex, :earmark_parser, "1.4.38", "b42252eddf63bda05554ba8be93a1262dc0920c721f1aaf989f5de0f73a2e367", [:mix], [], "hexpm", "2cd0907795aaef0c7e8442e376633c5b3bd6edc8dbbdc539b22f095501c1cdb6"}, "erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"}, - "ex_doc": {:hex, :ex_doc, "0.29.4", "6257ecbb20c7396b1fe5accd55b7b0d23f44b6aa18017b415cb4c2b91d997729", [:mix], [{:earmark_parser, "~> 1.4.31", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "2c6699a737ae46cb61e4ed012af931b57b699643b24dabe2400a8168414bc4f5"}, + "ex_doc": {:hex, :ex_doc, "0.30.9", "d691453495c47434c0f2052b08dd91cc32bc4e1a218f86884563448ee2502dd2", [:mix], [{:earmark_parser, "~> 1.4.31", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "d7aaaf21e95dc5cddabf89063327e96867d00013963eadf2c6ad135506a8bc10"}, "file_system": {:hex, :file_system, "0.2.10", "fb082005a9cd1711c05b5248710f8826b02d7d1784e7c3451f9c1231d4fc162d", [:mix], [], "hexpm", "41195edbfb562a593726eda3b3e8b103a309b733ad25f3d642ba49696bf715dc"}, - "makeup": {:hex, :makeup, "1.1.0", "6b67c8bc2882a6b6a445859952a602afc1a41c2e08379ca057c0f525366fc3ca", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "0a45ed501f4a8897f580eabf99a2e5234ea3e75a4373c8a52824f6e873be57a6"}, + "makeup": {:hex, :makeup, "1.1.1", "fa0bc768698053b2b3869fa8a62616501ff9d11a562f3ce39580d60860c3a55e", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "5dc62fbdd0de44de194898b6710692490be74baa02d9d108bc29f007783b0b48"}, "makeup_elixir": {:hex, :makeup_elixir, "0.16.1", "cc9e3ca312f1cfeccc572b37a09980287e243648108384b97ff2b76e505c3555", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "e127a341ad1b209bd80f7bd1620a15693a9908ed780c3b763bccf7d200c767c6"}, "makeup_erlang": {:hex, :makeup_erlang, "0.1.2", "ad87296a092a46e03b7e9b0be7631ddcf64c790fa68a9ef5323b6cbb36affc72", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "f3f5a1ca93ce6e092d92b6d9c049bcda58a3b617a8d888f8e7231c85630e8108"}, "makeup_graphql": {:hex, :makeup_graphql, "0.1.2", "81e2939aab6d2b81d39ee5d9e13fae02599e9ca6e1152e0eeed737a98a5f96aa", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.1", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "3390ab04ba388d52a94bbe64ef62aa4d7923ceaffac43ec948f58f631440e8fb"}, - "mix_test_watch": {:hex, :mix_test_watch, "1.1.0", "330bb91c8ed271fe408c42d07e0773340a7938d8a0d281d57a14243eae9dc8c3", [:mix], [{:file_system, "~> 0.2.1 or ~> 0.3", [hex: :file_system, repo: "hexpm", optional: false]}], "hexpm", "52b6b1c476cbb70fd899ca5394506482f12e5f6b0d6acff9df95c7f1e0812ec3"}, + "mix_test_watch": {:hex, :mix_test_watch, "1.1.1", "eee6fc570d77ad6851c7bc08de420a47fd1e449ef5ccfa6a77ef68b72e7e51ad", [:mix], [{:file_system, "~> 0.2.1 or ~> 0.3", [hex: :file_system, repo: "hexpm", optional: false]}], "hexpm", "f82262b54dee533467021723892e15c3267349849f1f737526523ecba4e6baae"}, "nimble_parsec": {:hex, :nimble_parsec, "1.4.0", "51f9b613ea62cfa97b25ccc2c1b4216e81df970acd8e16e8d1bdc58fef21370d", [:mix], [], "hexpm", "9c565862810fb383e9838c1dd2d7d2c437b3d13b267414ba6af33e50d2d1cf28"}, - "opentelemetry_api": {:hex, :opentelemetry_api, "1.2.0", "454a35655b4c1924405ef1f3587f2c6f141bf73366b2c5e8a38dcc619b53eaa0", [:mix, :rebar3], [], "hexpm", "9e677c68243de0f70538798072e66e1fb1d4a2ca8888a6eb493c0a41e5480c35"}, - "opentelemetry_process_propagator": {:hex, :opentelemetry_process_propagator, "0.2.1", "20ac37648faf7175cade16fda8d58e6f1ff1b7f2a50a8ef9d70a032c41aba315", [:mix, :rebar3], [{:opentelemetry_api, "~> 1.0", [hex: :opentelemetry_api, repo: "hexpm", optional: false]}], "hexpm", "f317237e39636d4f6140afa5d419e85ed3dc9e9a57072e7cd442df42af7b8aac"}, + "opentelemetry_api": {:hex, :opentelemetry_api, "1.2.2", "693f47b0d8c76da2095fe858204cfd6350c27fe85d00e4b763deecc9588cf27a", [:mix, :rebar3], [{:opentelemetry_semantic_conventions, "~> 0.2", [hex: :opentelemetry_semantic_conventions, repo: "hexpm", optional: false]}], "hexpm", "dc77b9a00f137a858e60a852f14007bb66eda1ffbeb6c05d5fe6c9e678b05e9d"}, + "opentelemetry_process_propagator": {:hex, :opentelemetry_process_propagator, "0.2.2", "85244a49f0c32ae1e2f3d58c477c265bd6125ee3480ade82b0fa9324b85ed3f0", [:mix, :rebar3], [{:opentelemetry_api, "~> 1.0", [hex: :opentelemetry_api, repo: "hexpm", optional: false]}], "hexpm", "04db13302a34bea8350a13ed9d49c22dfd32c4bc590d8aa88b6b4b7e4f346c61"}, + "opentelemetry_semantic_conventions": {:hex, :opentelemetry_semantic_conventions, "0.2.0", "b67fe459c2938fcab341cb0951c44860c62347c005ace1b50f8402576f241435", [:mix, :rebar3], [], "hexpm", "d61fa1f5639ee8668d74b527e6806e0503efc55a42db7b5f39939d84c07d6895"}, "statistex": {:hex, :statistex, "1.0.0", "f3dc93f3c0c6c92e5f291704cf62b99b553253d7969e9a5fa713e5481cd858a5", [:mix], [], "hexpm", "ff9d8bee7035028ab4742ff52fc80a2aa35cece833cf5319009b52f1b5a86c27"}, - "telemetry": {:hex, :telemetry, "0.4.3", "a06428a514bdbc63293cd9a6263aad00ddeb66f608163bdec7c8995784080818", [:rebar3], [], "hexpm", "eb72b8365ffda5bed68a620d1da88525e326cb82a75ee61354fc24b844768041"}, + "telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"}, } From 708375ac3ddeea3f37e97fb48a1f8ed330cf284b Mon Sep 17 00:00:00 2001 From: Ben Wilson Date: Sat, 18 Nov 2023 08:21:14 -0500 Subject: [PATCH 34/36] tweak index handling in the path --- lib/absinthe/phase/document/execution/resolution.ex | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/absinthe/phase/document/execution/resolution.ex b/lib/absinthe/phase/document/execution/resolution.ex index a16a5c6f62..f5b01e5c19 100644 --- a/lib/absinthe/phase/document/execution/resolution.ex +++ b/lib/absinthe/phase/document/execution/resolution.ex @@ -292,7 +292,7 @@ defmodule Absinthe.Phase.Document.Execution.Resolution do end defp maybe_add_non_null_error([], nil, %Type.NonNull{}, path) do - [%{message: "Cannot return null for non-nullable field", path: path}] + [%{message: "Cannot return null for non-nullable field", path: Enum.reverse(path)}] end defp maybe_add_non_null_error([], value, %Type.NonNull{of_type: %Type.List{} = type}, path) do @@ -300,9 +300,10 @@ defmodule Absinthe.Phase.Document.Execution.Resolution do end defp maybe_add_non_null_error([], [_ | _] = values, %Type.List{of_type: type}, path) do - Enum.with_index(values) + values + |> Enum.with_index() |> Enum.flat_map(fn {value, index} -> - maybe_add_non_null_error([], value, type, path ++ [index]) + maybe_add_non_null_error([], value, type, [index | path]) end) end From f69e0d077c812e0a5bb0aeaab24f8541a74afba8 Mon Sep 17 00:00:00 2001 From: Ben Wilson Date: Sat, 18 Nov 2023 08:22:45 -0500 Subject: [PATCH 35/36] changelog entry --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 42eb3b6972..8f95305cd2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## Unreleased - Bugfix: [Handle non_null(list_of(:thing)) with null list elements properly](https://github.com/absinthe-graphql/absinthe/pull/1259) +- Bugfix: [More non null result handling improvements](https://github.com/absinthe-graphql/absinthe/pull/1275) ## 1.7.4 From 5b5f24f919bd42e4f6ff9fb8aa90436508a3f8b9 Mon Sep 17 00:00:00 2001 From: Ben Wilson Date: Sat, 18 Nov 2023 08:25:12 -0500 Subject: [PATCH 36/36] bump version --- mix.exs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mix.exs b/mix.exs index 2a16c11d26..61d5c58b38 100644 --- a/mix.exs +++ b/mix.exs @@ -2,7 +2,7 @@ defmodule Absinthe.Mixfile do use Mix.Project @source_url "https://github.com/absinthe-graphql/absinthe" - @version "1.7.5" + @version "1.7.6" def project do [